Java反射

181 阅读8分钟

为什么会有反射机制?

假如现在有一个配置文件,里面有path=com.study.reflection类的全路径和method=fn方法名,传统的方式能否创建出这个类并且调用它的fn方法呢?答案是不能的

传统的方式想创建一个对象只能new,但是这样的需求在学习框架时特别多,即通过外部文件配置,在不修改源码情况下来控制程序,也符合设计模式的OCP原则(开闭原则:不修改源码,扩容功能)

反射创建对象如下所示

package src.month09.day0922.demo02;

import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;

public class Demo02 {
    public static void main(String[] args) throws Exception {

        String path = "src.month09.day0922.demo02.Cat";
        String method = "run";
        String property = "age";

        // Class.forName()用来加载类,返回一个src.month09.day0922.demo02.Cat对应的Class类型的对象aClass
        Class<?> aClass = Class.forName(path);// <?>表示不确定类型
        // 通过aClass对象得到加载的src.month09.day0922.demo02.Cat实例
        Object o = aClass.newInstance();
        
        // 1 通过aClass得到src.month09.day0922.demo02.Cat类的 run 方法对象
        // 在反射里,可以把方法也视为对象
        Method method1 = aClass.getMethod(method);
        // 得到方法对象后,可以在invoke方法中传入对象,调用对象身上的方法
        method1.invoke(o);
        
        // 2 通过aClass得到age属性对象
        Field age = aClass.getField(property);
        // 通过属性对象调用get方法,传入对象,得到属性值
        System.out.println(age.get(o));
        
        // 3 通过aClass得到构造器对象
        Constructor<?> constructor = aClass.getConstructor();
    }
}
  1. 反射机制允许程序在执行期借助于Reflection API取得任何类的内部信息(比如成员变量,构造器,成员方法等等),并能操作对像的属性及方法,反射在设计模式和框架底层都会用到
  2. 加载完类之后,在堆中就产生了一个Class类型的对象(一个类只有一个Class对象),这个对象包含了类的完整结构信息,通过这个对象得到类的结构,这个Class对象就像一面镜子,透过这个镜子看到类的结构,所以形象的称之为:反射

反射机制可以干嘛

  1. 在运行时判断任意一个对象所属的类
  2. 在运行时构造任意一个类的对象
  3. 在运行时得到任意一个类所具有的成员变量和方法
  4. 在运行时调用任意一个对象的成员变量和方法
  5. 生成动态代理

反射优缺点

  1. 优点:可以动态的创建和使用对像(也是框架底层核心),使用灵活,没有反射机制,框架技术就失去底层支撑
  2. 缺点:使用反射基本是解释执行,对执行速度有影响

反射调用优化-关闭访问检查

  1. Method,Field和Constructor对象都有setAccessible()方法
  2. setAccessible作用是启动和禁用访问安全检查的开关
  3. 参数值为true表示反射的对象在使用时取消访问检查,提高反射的效率,参数值为false则表示反射的对象执行访问检查

Class类

  1. Class也是类,因此也继承Object类
  2. Class类对象不是new出来的,而是系统创建的
  3. 对于某个类的Class类对象,在内存中只有一份,因为类只加载一次
  4. 每个类的实例都会记得自己是由哪个Class实例所生成
  5. 通过Class对象可以完整地得到一个类的完整结构,通过一系列API
  6. Class对象是存放在堆的
  7. 类的字节码二进制数据,是放在方法区的,有的地方称为类的元数据(包括方法代码,变量名,方法名,访问权限等等)

Class类的常用方法

static Class forName(String name) 返回指定类名name的Class对象

Object newInstance() 调用缺省构造函数,返回该Class对象的一个实例

getName() 返回此Class对象所表示的实体(类,接口,数组类,基本类型等)名称

Class getSuperClass() 返回当前Class对象的父类的Class对象

Class [] getInterfaces() 获取当前Class对象的接口

ClassLoader getClassLoader() 返回该类的类加载器

Class getSuperclass() 返回表示此Class所表示的实体的超类的Class

Constructor[] getConstructors() 返回一个包含某些Constructor对象的数组

Field[] getDeclaredFields() 返回Field对象的一个数组

Method getMethod(String name,Class ...paramTypes) 返回一个Method对象,此对象的形参类型为paramType

getPackage().getName 得到包名

getName() 得到全类名

getField() 获得属性,的到属性后可以用set()赋值

Field[] getFields() 获取所有属性

获取Class类对象的6种方式

不同阶段有不同获取方式

  • 代码/编译阶段通过 Class.forName() 获取
  • Class类阶段(加载阶段)通过 类.class 获取
  • 运行阶段通过 对象.getClass() 获取
  1. 前提:已知一个类的全类名,且该类在类路径下,可通过Class类的静态方法forName()获取,可能抛出ClassNotFoundException,实例:Class cls1 = Class.forName("java.lang.Cat"); 应用场景:多用于配置文件,读取类全路径,加载类

  2. 前提:若已知具体的类,通过类的class获取,该方式最为安全可靠,程序性能最高,实例:Class cls2=Cat.class; 应用场景:多用于参数传递,比如通过反射得到对应构造器对象

  3. 前提:已知某个类的实例,调用该实例的getClass()方法获取Class对象,实例:Class clazz = 对象.getClass(); 应用场景:通过创建好的对象,获取Class对象

  4. 其他方式

    (1)先得到类加载器

    ClassLoader cl = 对象.getClass().getClassLoader();

    (2)通过类加载器得到Class对象

    Class clazz4 = cl.loadClass("类的全类名");

  5. 基本数据(int,char,boolean,float,double,byte,long,short)按如下方式得到Class类对象

    Class cls = 基本数据类型.class

  6. 基本数据类型对应的包装类,可以通过.type得到Class类对象

    Class cls = 包装类.TYPE

哪些类型有Class对象

外部类,内部类,接口,数组,注解,枚举,基本数据类型,void,Class

反射机制是java实现动态语言的关键,也就是通过反射实现类动态加载

  1. 静态加载:编译时加载相关的类

    如:Dog dog = new Dog();如果没有则报错,依赖性太强

    1. 动态加载:运行时加载需要的类,如果运行时不用该类,即使该类不存在,也不报错,降低了依赖性
    Class<?> aClass == Class.forName("com.study.Dog");
    Object o = aClass.newInstance();
    
  2. 类加载时机

    1. 当创建对象时(new) 静态加载
    2. 当子类被加载时 静态加载
    3. 调用类中的静态成员时 静态加载
    4. 通过反射 动态加载

类的加载过程

源代码Cat.java ==javac编译==> 字节码文件Cat.class ==java运行==> 类加载

其中类加载有三个阶段: 加载, 连接, 初始化

  • 加载:将类的class文件读入内存,并为之创建java.lang.Class对象,此过程由类加载器完成

  • 连接:将类的二进制数据合并到 JRE 中

  • 初始化:JVM负责对类进行初始化,这里主要指静态成员

类加载后内存布局情况

  • 方法区内存放类的字节码二进制数据

  • 堆内存放类的Class对象

通过反射获取类的结构信息

第一组:java.lang.Class类

getName:获取全类名
getSimpleName:获取简单类名
getFields:获取所有public修饰的属性,包含本类以及父类的
getDeclaredFields:获取本类中所有属性
getMethods:获取所有public修饰的方法,包含本类以及父类的
getDeclaredMethods:获取本类中所有方法
getConstructors:获取所有public修饰的构造器,包含本类以及父类的
getDeclaredConstructors:获取本类中所有构造器
getPackage:以Package形式返回包信息
getSuperClass:以Class形式返回父类信息
getlnterfaces:以Class[]形式返回接口信息
getAnnotations:以Annotation[]形式返回注解信息

第二组:java.lang.reflect.Field类

getModifiers:以int形式返回修饰符 (说明:默认修饰符是0,public是1,private是2,protected是4,static是8,final是16)
getType:以Class形式返回类型
getName:返回属性名

第三组:java.lang.reflect.Method类

getModifiers:以int形式返回修饰符 (说明:默认修饰符是0,public是1,private是2,protected是4,static是8,final是16)
getReturnType:以Class形式获取返回类型
getName:返回方法名
getParameterTypes:以Class[]返回参数类型数组

第四组:java.lang.reflect.Constructor类

getModifiers:以int形式返回修饰符
getName:返回构造器名(全类名)
getParameterTypes:以Class返回参数类型数组

反射爆破

通过反射机制,可以访问对象中的私有属性和方法

访问构造器,创建实例

package src.month09.day0922.demo03;

import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;

public class Demo03 {
    public static void main(String[] args) throws ClassNotFoundException, InstantiationException, IllegalAccessException, NoSuchMethodException, InvocationTargetException {
        // 1 先获取到User类的Class对象
        Class<?> userClass = Class.forName("src.month09.day0922.demo03.User");

        // 2 通过public的无参构造器创建实例
        Object o = userClass.newInstance();
        System.out.println(o);

        // 3 通过public的有参构造器创建实例
        Constructor<?> constructor = userClass.getConstructor(String.class);
        // constructor对象就是
        // public User(String name) {// public
        //     this.name = name;
        // }
        Object tom = constructor.newInstance("tom");
        System.out.println(tom);

        // 4 通过非public的有参构造器创建实例
        Constructor<?> constructor1 = userClass.getDeclaredConstructor(String.class, int.class);
        constructor1.setAccessible(true);// 爆破:使用反射可以访问私有构造器/属性/方法,在反射面前一切都是纸老虎,类似于留了个后门
        Object jerry = constructor1.newInstance("jerry", 12);
        System.out.println(jerry);
    }
}

class User {
    private String name;
    private int age;

    public User() {// 无参
    }

    public User(String name) {// public
        this.name = name;
    }

    private User(String name, int age) {// private
        this.name = name;
        this.age = age;
    }

    @Override
    public String toString() {
        return name + "\t" + age;
    }
}

访问属性

package src.month09.day0922.demo04;

import java.lang.reflect.Field;

public class Demo04 {
    public static void main(String[] args) throws ClassNotFoundException, InstantiationException, IllegalAccessException, NoSuchFieldException {
        // 1 得到Student类对应的Class对象
        Class<?> stuClass = Class.forName("src.month09.day0922.demo04.Student");
        // 2 创建对象
        Object o = stuClass.newInstance();// o的运行类型示Student
        // 3 拿到对象的age属性,公共的
        Field age = stuClass.getField("age");
        age.set(o, 34);// 通过反射设置属性
        System.out.println(o);// null	34
        // 4 使用反射操作name属性,私有的
        Field name = stuClass.getDeclaredField("name");
        name.setAccessible(true);// 爆破,可以操作私有属性
        name.set(o, "jerry");
        System.out.println(o);// jerry	34
    }
}

class Student {
    private String name;// 私有
    public int age;// 公共

    public Student() {
    }

    public Student(String name, int age) {
        this.name = name;
        this.age = age;
    }

    @Override
    public String toString() {
        return name + "\t" + age;
    }
}

访问方法

package src.month09.day0922.demo04;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

public class Demo04 {
    public static void main(String[] args) throws ClassNotFoundException, InstantiationException, IllegalAccessException, NoSuchMethodException, InvocationTargetException {
        // 1 得到Student类对应的Class对象
        Class<?> stuClass = Class.forName("src.month09.day0922.demo04.Student");
        // 2 创建对象
        Object o = stuClass.newInstance();
        // 3 调用public的eat方法
        Method eat = stuClass.getMethod("eat");
        eat.invoke(o);
        // 4 调用private的day方法
        Method say = stuClass.getDeclaredMethod("say");
        say.setAccessible(true);// 爆破
        say.invoke(o);
    }
}

class Student {
    public void eat() {
        System.out.println("eat");
    }

    private void say() {
        System.out.println("hello");
    }
}