反射我还没具体用到过,只学习了理论知识,这里叙述下学习到的知识链:
什么是反射?为什么叫做反射?
通过某一种方式获取对象的Class实例对象(这里的对象指的是类的对象,如Person.class),通过这个Class对象(Class demo = Person.class; )反射出这个类的信息。
反射有什么用?
反射可以用来在运行时获取类的信息。
既然反射是用来获取类的信息的,那可以先看下他获取类的属性方法:
前置条件:获取对象的Class实例对象,我所知道的有三种方法,应用在不同的场景:
- 第一种,通过类名获取:Class c1 = Person.class;
- 第二种,通过类在项目中的位置获取: Class c2 = Class.forName("com.cai.reflection.Person");
- 第三种,通过类的实例对象获取: Person person = new Person(); Class c3 = person.getClass();
三种方式获取的所需条件各不同,可以根据实际所有条件决定使用哪种方法,这里详细解释下Classname.class和Class.forName这两种的具体区别,主要是是否触发静态代码块的区别,demo如下:
我们在Person中加入一个静态游离代码块
我们先使用forName方法获取Class对象试试:
可以在控制台看到静态代码块被触发了
接着使用Person.class这种方式试一下
控制台并没有输出什么,原因要追溯到Java类的装载顺序,这里引用jiese1990一张图片:
可以看到,静态代码块的执行是最后初始化步骤才执行的,初始化的触发条件可以看Oracle文档:https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-5.html
其中的5.5初始化部分提及触发初始化的一些条件,其中有一条这么说到:
Invocation of certain reflective methods in the class library (§2.12), for example, in class Class
or in package java.lang.reflect
.
类加载的初始化阶段只有以下六种主动使用类的情况会触发:
1、创建类的实例
2、访问某个类或接口的静态变量,或者对该静态变量赋值
3、调用某个类的静态方法
4、反射(Class.forName("java.lang.String"))
5、初始化一个类的子类
6、Java虚拟机启动时被标明为启动类的类
Class.forName这种方法是属于其中第四点,而Person.class这种在load阶段已经加载完成了,不会触发初始化。
我们查看一下forName的源码可以发现:
可以看到forName底层使用的也是 ClassLoader,ClassLoader中的loadClass只做Class加载,并不做初始化步骤,所以ClasssLoader不会触发静态代码块。
但是forName内提供了一个参数供我们选择是否触发静态代码块的初始化,这也就是为什么forName同样用的是classLoader却能够触发类加载的初始化步骤。
接下来以获取一个父类属性为例子演示反射的使用过程:
我们再建一个Student类,继承自Person类,我们要通过这个Student类获取Person类的属性方法:
第一步自然是获取Student的Class实例
Class demo = Student.class;
接着就是反射的使用内容了,demo中有许多方法可以获取到Student的内容:
- getName():获得类的完整名字。
- getFields():获得类的public类型的属性。
- getDeclaredFields():获得类的所有属性。包括private 声明的和继承类
- getMethods():获得类的public类型的方法。
- getDeclaredMethods():获得类的所有方法。包括private 声明的和继承类
- getMethod(String name, Class[] parameterTypes):获得类的特定方法,name参数指定方法的名字,parameterTypes 参数指定方法的参数类型。
- getConstructors():获得类的public类型的构造方法。
- getConstructor(Class[] parameterTypes):获得类的特定构造方法,parameterTypes 参数指定构造方法的参数类型。
- newInstance():通过类的不带参数的构造方法创建这个类的一个对象。
- .getSuperClass():获取父类Class实例。
我们要通过student获取其父类Person的属性,自然先要考虑 获取其父类的Class,这里写一个方法获取:
public static Field getDeclaredField(Class demo, String fieldName) {
Field field = null;
// 父级field的获取,demo为本层的Class实例,fieldName为要获取的父级属性名
// field获取过程:主要通过getSuperclass方法,循环是为了判断这个类是否已经取到了超类Object的Class实例,这样确保可以取到所要的层级属性,而不仅仅局限于一两个层级。
for(; demo != Object.class; demo = demo.getSuperclass()) {
try {
field = demo.getDeclaredField(fieldName);
field.setAccessible(true);
return field;
} catch (Exception e) {
}
}
return null;
}
这里刚看的时候觉得setAccessible有点多余,以为getDeclaredField已经可以获取所以权限的属性字段了,为什么还要设置运行访问,原来这个运行访问是后面在通过该field获取内容的时候给予的权限,简而言之就是该设置是设置允许访问字段属性的内容,而不是获取字段属性的权限。
内容到这里差不多结束了,获取到了field之后,想要什么内容自己取即可,下面给上完整例子:
public class ReflectionTest {
@Test
public void testGetParentFields() throws Exception {
// 获取子类的Class实例
Class demo = Student.class;
System.out.println(getFieldValue(demo, "name"));
}
@Test
public void testStaticArea() {
// 这样并不会初始化Class对象,Classname.class这种获取对象类实例的方法不属于反射方法。
Class c1 = Person.class;
try {
// 这种会初始化Class对象,Class.forName()属于主动使用中的反射,它是一个要求装载后立刻初始化加载类的操作。
// 但是他有提供第二个参数选择是否初始化静态代码块,
Class c2 = Class.forName("com.cai.reflection.Person");
} catch (Exception e) {
e.printStackTrace();
}
}
public static Field getDeclaredField(Class demo, String fieldName) {
Field field = null;
// 父级field的获取,demo为本层的Class实例,fieldName为要获取的父级属性名
// field获取过程:主要通过getSuperclass方法,循环是为了判断这个类是否已经取到了超类Object的Class实例,这样确保可以取到所要的层级属性
for(; demo != Object.class; demo = demo.getSuperclass()) {
try {
field = demo.getDeclaredField(fieldName);
field.setAccessible(true);
return field;
} catch (Exception e) {
}
}
return null;
}
public static Object getFieldValue(Class demo, String fieldName) throws Exception {
Field field = getDeclaredField(demo, fieldName);
return field.get(demo.newInstance());
}
}