反射相关
什么是反射?
JAVA反射技术是在运行状态中:
- 对于任意一个类,都能够知道这个类的所有属性和方法(包括私有)
- 对于任意一个对象,都能够调用它的任意一个方法,访问任意一个属性。
这种动态获取类型信息,以及动态调用对象方法的功能,被称为Java语言的反射机制。
总之,反射是提供获取运行时类信息的一种技术,或者更通俗一点反射技术就是操作一个类的Class对象
。
反射可以在程序运行的过程中,动态地加载一个类,并且能拿到一个类的全部详细信息,从而可以操作该类的属性和方法。反射技术源于Class对象
,其本质就是通过一个类的Class对象
来获得该类的信息,并操作该类的属性和方法。反射技术应用的起点就是获取某个类的Class对象
。那么什么是Class对象
?
关于Class对象
什么是Class对象
?用来描述一个类的对象。Class对象
作为一个对象,其也是由类创建出来的,这个类就是java.lang
包下的Class
类,Class类
没有公共构造函数,Class对象
是由Java虚拟机在加载类时自动构建的,通过调用类加载器中的defineClass
方法来构建。类加载的过程可以分为三步:加载,链接,初始化。其中的第一步:加载,完成的功能就是将一个类的字节码文件读到方法区,并在堆区生成相应的Class对象
。
Class对象
里面记录着每个类的运行时数据结构,基本包括类的所有信息,包括属性,方法,注解等等。基本Java
里面的所有类型都有Class对象
:
- 普通的
class类
(外部类,内部类) - 接口
interface
[]
数组- 枚举
enum
- 注解
@interface
- 基本数据类型
(boolean,byte,char,int,float,double,long)
void
注意:如果两数组的维数以及元素的数据类型相同,那么他们的Class对象
是同一个。
获取Class对象
一般上获取Class对象
有4种方式
Class.forName(“全限定类名”)
:此方法是Class类
的静态成员方法,此方法会触发类加载阶段的最后一个阶段:类初始化阶段(执行Clinit()
方法)1
Class stringClass = Class.forName("java.lang.String");
对象名.getClass()
:此方法是Object类
的方法,因此所有的类中都会继承该方法,但是需要用对象来调用。此方法会触发类加载阶段的最后一个阶段:类初始化阶段(执行Clinit()
方法)1
Class stringClass = "ZS".getClass();
类名/接口(类字面常量).class
:此方法并不会触发类加载阶段的最后一个阶段:类初始化阶段(不会执行Clinit()
方法)意味着该方法获得Class对象
时不会自动初始化该Class对象
,而是延迟到静态成员访问或静态方法调用的时候才初始化。(如果一个成员变量同时被static
和final
修饰,被称为“编译时常量”,在编译期就已经将其放进常量池,访问这个常量,并不会触发类加载,但是如果只是被他们的其中的一个修饰,则没有这个效果)1
Class personClass = Person.class;
(类加载器)ClassLoader
1
2ClassLoader classLoader = Person.class.getClassLoader();
Class<?> pClass = classLoader.loadClass("com.xiaobin.Person");
因为类加载只有一次,因此同一个类的Class对象
也只有一个,通过上面不同方法获取到的同一个类的Class对象
是同一个对象。
反射的相关用法
Class对象
是反射的入口,反射的几乎所有用法都是在使用Class类
里面的相关方法,完成一些功能。对于一个类我们获取了其相应的Class对象
,那么我们可以获取到它的所有信息(包括私有的)意味着我们可以做很多事情。
属性相关的操作
获取属性
通过Class对象
可以获取一个类的所有属性,相关方法如下:
获取子类以及父类中的一个对应public权限的属性:
1
Field value = aClass1.getField(属性名);//获取相应名字的public属性,包含父类的public属性
获取子类中声明过的所有属性(包括private的)
1
Field value1 = aClass1.getDeclaredField(属性名);//获取相应名字的属性(可以是private的),但不包含父类的任何属性
获取子类和父类中的所有public属性:
1
Field[] fields = aClass1.getFields();//获取所有的public属性,包括父类中的所有public属性
获取本类中声明过的所有属性(包括private的)
1
Field[] declaredFields = aClass1.getDeclaredFields();//获取本类中所有的属性(含private的),但是不包含父类的任何属性
以上的方法的特点:
方法名后面有
s
的就是获取所有的属性,返回的是属性的数组(File
类型数组,里面的每个元素就是一个属性)方法名有
Declared
(宣布的,声明的)的表示只能获取到本类声明的属性(public是声明的、private也是声明的,所以能获取到本类中的所有属性,包括private)通过一个子类的
Class对象
是没有办法直接获取到父类中的private属性的Field
,Method
或Constructor
类的。要想获取到父类中的private属性,要先获取到父类的Class对象
,如下:1
2Class<?> superclass = aClass1.getSuperclass();
Field value = superclass.getDeclaredField(父类私有属性名); 但是子类会隐式继承父类的private属性的成员,而且在通过以上的方法获得父类中private权限的Field、Method、Constructor之后,通过传进一个子类对象,就可以访问到子类中通过隐式继承得到的父类的private成员,具体如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39public class Demo7 {
public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException, NoSuchMethodException, InvocationTargetException {
Son son = new Son(666);
Class<? extends Son> sonClazz = son.getClass();
// 获取父类类型Class对象
Class<?> fatherClazz = sonClazz.getSuperclass();
Field numFiled = fatherClazz.getDeclaredField("num");
// 破解访问权限
numFiled.setAccessible(true);
// 输出子类对象中a的结果(注意这里虽然是通过父类的Class对象获取到的Field类对象,但是是将子类的对象传进去的,表示获取子类对象中的这个成员)
System.out.println(numFiled.get(son));
// 子类对象调用父类私有成员方法
Method sayHiMethod = fatherClazz.getDeclaredMethod("sayHi");
sayHiMethod.setAccessible(true);
//这里也是一样的,虽然是通过父类的Class对象获取到的Method类对象,但是调用的时候传进取得对象是子类对象,表示调用的是子类的这个方法。
Object o = sayHiMethod.invoke(son);
// 无返回值,o是null
System.out.println(o);
}
}
class Father {
private int num;
public Father(int num) {
this.num = num;
}
private void sayHi(){
System.out.println("你好啊,朋友!");
}
}
class Son extends Father {
public Son(int num) {
super(num);
}
}以上的程序输出的结果为:
1
2
3666
你好啊,朋友!
null返回的是一个
Field
类的对象,这个Field
类对象里面封装了对应类的属性信息:getName()
:返回字段名称,例如,"name"
;getType()
:返回字段类型,也是一个Class
实例,例如,String.class
;getModifiers()
:返回字段的修饰符,它是一个int
,不同的bit表示不同的含义。
比如
String类
底层存储字符串的是一个char[]
,它的定义如下:1
2
3public final class String {
private final char[] value;
}1
2
3
4
5
6
7
8
9
10
11
12
13
14//获取String类的Class对象
Class strClass = Class.forName(“java.lang.String”);
//获取成员变量
Field field = strClass.getDeclaredField("value");
//获取该成员变量的名
String name = field.getName(); --> "value"
//获取该成员变量的类型
Class<?> type = field.getType(); --> class [C 表示为一个char类型的数组
//获取该成员的modifiers,返回的是一个int型的值,不同的表示的内容不一样,通过Modifier.isXXX()方法可以获得该成员变量的修饰情况
int modifiers = field.getModifiers();
boolean aFinal = Modifier.isFinal(modifiers); --> true
boolean aPublic = Modifier.isPublic(modifiers); --> false
boolean aPrivate = Modifier.isPrivate(modifiers); --> true
boolean aStatic = Modifier.isStatic(modifiers); --> false
设置属性值
在获取到相应的属性之后,我们可以给相应的属性赋值。
在这之前要先解决运行时访问检查的事情。
当Field、Method或Constructor(三者都是反射对象)分别用于设置字段(
set(Object obj, Object value)
)或获取字段(get(Object obj)
)、调用方法(invoke(Object obj, Object... args)
)或创建和初始化类的新实例(newInstance(Object... initargs)
)时,将执行运行时访问检查。主要是检查相关的操作是否有权限等等。
AccessibleObject类
,是Field
、Method
、Constructor
的公共父类。通过继承该类,Field
、Method
、Constructor
获得了一个方法setAccessible(boolean flag)
方法,可以设置对一个操作是否进行访问检查。
accessible
默认为false,表示会进行运行时访问检查。注意:就算是public权限的也会进行访问检查,只是访问检查后对其操作没有影响;而protected和private权限的进行访问权限检查之后可能会由于权限不够而报异常。- 可以通过
setAccessible(true)
取消运行访问检查,这样就可以任意访问任何权限的内容。同时因为运行访问检查要耗费一定的时间,因此跳过运行访问检查也可以提升反射的运行速度。
在解决完运行时访问检查之后,我们可以对任意的一个属性进行赋值操作:
获取相应的属性的值:
1
Field.get(对象a名);
表示获取对象a的相应的属性的值
设置属性的值:
1
Field.set(对象a名,想设置的值);
表示将对象a的相应属性设置为对应的值。
获取构造器、创建对象实例
通过一个Class对象
,我么可以获取到相应类的构造器。
相应的方法如下:
获得一个public权限的构造器(因为构造器不能被继承,所以这里没有父类的构造器的事情):
1
Constructor<Son> constructor = sonClass.getConstructor(Integer.class , String.class);//根据传入的参数类型返回一个对应的public权限的构造器
获得本类中的所有构造器,包括private修饰的:
1
Constructor<Son> constructor = sonClass.getDeclaredConstructor(Integer.class , String.class);//根据传入的参数类型返回一个对应的构造器(任何权限都可以返回)
获得所有public修饰的构造器:
1
Constructor<?>[] constructors = sonClass.getConstructors();//获取所有的public的构造器
获得所有的构造器,包括private修饰的:
1
Constructor<?>[] declaredConstructors = sonClass.getDeclaredConstructors();//获取所有的构造器
获取到构造器之后就可以在程序运行的时候动态创建一个对象实例。
通过:Son son1 = constructor.newInstance(1, 2, "123");
通过传进来相应的值创建一个实例。
另外还可以通过Class对象.newInstance()
方法来创建一个实例,但是这样只能通过无参构造器来创建实例。
获取并调用方法
通过Class对象
获取类里面的方法:
1 | Method method1 = sonClass.getMethod(方法名 , 形参列表Calss);//获取相应的public方法 |
调用方法:
1 | method.invok(对象名,实参列表);//调用相应对象的方法,并传入实参,该实参列表是可变参数,因此可以使用Object[]进行传参 |
另外通过反射进行的方法调用仍然遵循多态,运行时总是呈现实际对象的结果
获取注解信息
通过反射可以获取一个类,类的方法,属性的注解,并拿到里面的相应的信息,这是许多框架的通用做法。
判断一个类上是否有相应的注解标记:
1
boolean annotationPresent = sonClass.isAnnotationPresent(注解名.class);
获得类上的注解:
1
注解名 annotation = (注解名) sonClass.getAnnotation(注解名.class);
拿到注解后,可以进一步拿到注解里面的信息。比如一个注解里面有一个
value
属性,则可以通过以下方法来获得:1
String value = annotation.value();