1. 什么是反射?
Java反射可以在程序运行的时候获取类和对象的所有信息,并可以使用它的字段和方法,大量应用于框架中。
2. 反射的优缺点
2.1. 优点
极大的提高了程序的 灵活性
和 扩展性
,它可以允许程序创建和控制任何类的对象。
2.2. 缺点
- 安全问题:,由于可以使用
setAccessible(true)
方法设置为可被强制访问、可以无视泛型参数的安全检查等。 - 性能问题:由于反射需要检查方法可见性、进行参数校验、JVM无法进行优化等原因导致反射的性能相对较差。
3. Class类
在Java中,除了基础类型(比如int)等,其他所有的类型都是 class,包括interface。
class
是由JVM动态加载的,JVM会在第一次读取到class
的时候,将其加载进内存。而每加载一种class,JVM就会为其创建一个Class类型的实例,并将该class类型和这个Class的实例关联起来。
注意区分 Class 和 class,class是一种类型,而Class是一个名为Class的class。
那么如何创建一个Class实例,我们看下JDK中的Class类的源码
我们发现到,Class类的构造器是私有的,只有JVM可以创建Class实例,我们是不能主动创建Class实例的。
由于JVM为每一个加载的class类型创建了对应的Class实例,并且在实例中保存了该class类型的所有信息,包括类名、包名、方法、字段等,因此如果能够获取Class实例,我们就可以通过这个Class实例获取到该实例对应的class类型的所有信息。这种通过Class实例获取class信息的方法就是反射(Reflection)。
3.1. 获取class的Class实例
- 直接通过class的静态变量class获取:
Class cls = String.class;
- 通过class类型对应的Class实例的getClass()方法获取:
String str = "GuoziGe";
Class cls = str.getClass();
- 通过全限定名(完整包名)获取:
Class cls = Class.forName("java.lang.String");
3.2. 动态加载
JVM在执行Java程序的时候,并不是一次性将所有用到的class全部加载到内存,而是在第一次用到class的时候加载。
4. 访问字段
对于任何一个对象实例,只要我们获取到了它的Class,就可以获取到它的一切信息。
4.1. 获取字段信息
Class类提供了4个方法用于获取字段信息:
- Field getField(name):根据字段名获取某个public的field(包括父类)
- Field[] getFields():获取所有public的field(包括父类)
- Field getDeclaredField(name):根据字段名获取当前类的某个field(不包括父类)
- Field[] getDeclaredFields():获取当前类的所有field(不包括父类)
注意:不能通过子类的Class直接获取父类的私有属性,当然可以通过子类的Class获取到父类的Class后间接获取。
我们定义两个类
分别测试一下上面的四个方法
输出为:
4.2. 获取字段值
上面我们已经可以使用反射获取一个对象实例的字段Field实例,使用该实例可以获取该字段的值等。
我们看下Field的源码
发现包含了很多字段的信息,比如字段名、字段类型、字段修饰符、修饰的注解等等。我们可以使用字段实例的field.get(对象实例)方法获取字段值。
来,还是上代码看看
输出为:
哎?我们发现了问题,grade是可以正常获取到值的。但是score(私有字段)无法正常取到值,触发了异常(IllegalAccessException)非法访问,不能直接访问学生类的私有字段。
那怎么解决呢?当然我们可以直接将private改成public(手动滑稽),获取再获取值之前,使用setAccessible(true)方法将其强制可访问。来看看效果:
正常输出。
那么为啥可以的,如果可以通过反射获取private的值的话,那么封装还有什么意义呢?
其实,我们在日常的实际开发过程中,一般都是通过student.name来获取值,编译器会根据public、private等来决定是否可以允许访问字段,以达到封装的目的。
而反射是一种非常规的用法,它更多给框架使用。
注意:setAccessible(true)方法可能失败,JVM运行时如果有安全检查则会阻止,比如不允许java、javax包下的类调用,保证JVM核心库的安全性。
4.3. 设置字段值
同上,我们使用了field.get(对象实例)来获取字段值,我们可以使用field.set(对象实例, 需要设置的数据)来设置字段值。
5. 调用方法
5.1. 获取方法信息
同样的,Class中也提供了四个方法用于获取方法信息:
- Method[] getMethods():获取所有public的Method(包括父类)
- Method getMethod(name, Class…):获取某个public的Method(包括父类)
- Method[] getDeclaredMethods():获取当前类的所有Method(不包括父类)
- Method getDeclaredMethod(name, Class…):获取当前类的某个Method(不包括父类)
我们重载方法,参数分别为int类型和String类型。
5.2. 调用方法
同字段信息,方法信息中也包含了很多,比如方法名、方法的访问控制权限等等。
我们可以通过method.invoke(对象实例, 方法参数)调用方法。
同字段的使用,针对private出现非法访问的情况,我们可以在方法调用之前使用setAccessible(true)方法设置为可访问。
6. 调用构造方法
我们之前创建对象都是使用new关键词
Student s = new Student();
使用反射创建新的实例,可以通过Class中提供的newInstance()方法。
Student s = Student.class.newInstance();
不过使用上述方法只能调用该类的public无参构造方法。
为了调用任意的构造方法,Java反射机制提供了Constructor对象,包含了构造方法的所有信息,和Method类似。