Java反射机制
事情的起因是一道面向对象的作业题,老师:你们做了就会了。题目和解答见文章末尾
反射基础
用途
反射功能通常用于检查或修改Java虚拟机运行中(runtime)的应用程序的行为
在运行中分析类的能力,可以通过完全限定类名创建类的对象实例。
在运行中查看和操作对象,可以遍历类的成员变量。
反射允许代码执行非反射代码中非法的操作,可以检索和访问类的私有成员变量,包括私有属性、方法等。
注意:要有选择的使用反射功能,如果可以直接执行操作,那么最好不要使用反射。
反射的底层运作
通常,java在编译之后,会将Java代码生成为class源文件,JVM启动时,将会载入所有的源文件,并将类型信息存放到方法区中;将所有对象实例存放在Java堆中,同时也会保存指向类型信息的指针。
正常流程下,我们在创建类的实例之前,是明确知道这个类的类型信息的,如名字、方法、属性等,我们可以很容易地创建实例,并通过实例获取属性和调用方法。
如果我们不知道一个方法在实际运行时,需要处理的对象是谁,它的类型信息如何,那么该如何访问这个对象,或是创建一个新的实例呢?
与上述流程相反,我们需要先在方法区获取对象的类型信息,获取之后就可以知道该类的属性、方法、父子类等信息,此时便可以通过这些信息回调处理对象。
以上便是反射的原理,通过读取方法区中的字节码,来动态的找到其反射的类以及类的方法和属性等(实际上就是在运行时,根据全类型名在方法区找对应的类),用这些类型信息完成对该类实例的操作,其实就是直接使用类的一个逆向使用。
下面是一段抄来的代码,可以帮我们有效理解这个过程
1 | void reflectMethod(Object obj) { |
在Java中的使用
相关类
在这个包下
1 | java.lang.reflect.*; |
比较常用的一些类
类 | 含义 |
---|---|
java.lang.Class | 代表整个字节码。代表一个类型,代表整个类。 |
java.lang.reflect.Method | 代表字节码中的方法字节码。代表类中的方法。 |
java.lang.reflect.Constructor | 代表字节码中的构造方法字节码。代表类中的构造方法。 |
java.lang.reflect.Field | 代表字节码中的属性字节码。代表类中的成员变量(静态变量+实例变量)。 |
必须先获得Class才能获取Method、Constructor、Field
获取类型信息(Class)
要操作一个类的字节码,需要首先获取到这个类的字节码,有三种方式可以获取Class
方式 | 备注 |
---|---|
Class.forName(“完整类名带包名”) | 静态方法 |
对象.getClass() | |
任何类型.class |
以上三种方式返回值均为Class类型
Class.forName()
通过完全限定类名获取类,即包名+类名,否则会报错找不到类
1 | Class<HashMap> hashMapClass = Class.forName("java.util.HashMap"); |
Object.getClass()
从一个实例对象中获取它的类,这仅适用于继承自Object的引用类型(Java的类默认继承于Object)
1 | Map<String, String> hashMap = new HashMap<>(); |
xxx.class
直接从未实例化的类获取类
1 | Class<Integer> integerClass = int.class; |
通过反射实例化对象
1 | 对象.newInstance() |
newInstance()方法内部实际上调用了无参数构造方法,必须保证无参构造存在才可以。
否则会抛出java.lang.InstantiationException
异常。
下面是一颗栗子
1 | class ReflectTest02{ |
获取类的成员变量
获取属性
方法名 | 备注 |
---|---|
public Field[] getFields() | 返回类中public修饰的属性 |
public Field[] getDeclaredFields() | 返回类中所有的属性 |
public Field getDeclaredField(String name) | 根据属性名name获取指定的属性 |
public native int getModifiers() | 获取属性的修饰符列表,返回的修饰符是一个数字,每个数字是修饰符的代号【一般配合Modifier类的toString(int x)方法使用】 |
获取方法
方法名 | 备注 |
---|---|
public Method[] getDeclaredMethods() | 返回类中所有的实例方法 |
public Method getDeclaredMethod(String name, Class<?>… parameterTypes) | 根据方法名name和方法形参获取指定方法 |
获取构造器
方法名 | 备注 |
---|---|
public Constructor<?>[] getDeclaredConstructors() | 返回类中所有的构造方法 |
public Constructor getDeclaredConstructor(Class<?>… parameterTypes) | 根据方法形参获取指定的构造方法 |
其他
方法名 | 备注 |
---|---|
public native Class<? super T> getSuperclass() | 返回调用类的父类 |
public Class<?>[] getInterfaces() | 返回调用类实现的接口集合 |
Field类方法
获取字段类型
字段可以是基本类型或引用类型。
有八种基本类型:boolean,byte,short,int,long,char,float,和double。
引用类型是java.lang.Object类的直接或间接子类,包含接口,数组和枚举类型等 。
1 | Class<?> className = Class.forName("java.util.HashMap"); |
获取字段修饰符
修饰符:public、protected、private、static、final等
1 | Class<?> className = Class.forName("java.util.HashMap"); |
使用反射机制给属性赋值
1 | //使用反射机制给属性赋值 |
set()访问私有属性
不能直接访问,需要打破封装后才可以
1 | public void setAccessible(boolean flag) |
示例:
1 | Field nameField = studentClass.getDeclaredField("name"); |
反射修改final修饰的属性值
反射功能强大,能修改private以及final修饰的变量。如下代码中,展示了JVM的优化以及反射的一些劣势。
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
39
40
41
42
public class FieldReflectDemo {
// 引用直接指向常量池中的常量值
private final String constantStr = "FinalConstantStringField";
// JVM优化了getter方法,直接将对constantStr引用全部替换成了常量
// public String getConstantStr() {return "FinalConstantStringField";}
// 在堆中新建了一个对象
private final String newStr = new String("FinalNewStringField");
public FieldReflectDemo(){}
public static void main(String[] args) {
FieldReflectDemo fieldReflectDemo = new FieldReflectDemo();
try {
Class<?> className = fieldReflectDemo.getClass();
Field constantStr = className.getDeclaredField("constantStr");
Field newStr = className.getDeclaredField("newStr");
// 获取实例对象的字段值
System.out.println("constantStr原:" + constantStr.get(fieldReflectDemo));
System.out.println("newStr原:" + newStr.get(fieldReflectDemo));
constantStr.setAccessible(true);
newStr.setAccessible(true);
constantStr.set(fieldReflectDemo, "New Filed Name");
newStr.set(fieldReflectDemo, "New Filed Name");
System.out.println("constantStr反射修改:" + constantStr.get(fieldReflectDemo));
System.out.println("newStr反射修改:" + newStr.get(fieldReflectDemo));
} catch (NoSuchFieldException | IllegalAccessException e) {
e.printStackTrace();
}
System.out.println("constantStr实例对象值:" + fieldReflectDemo.getConstantStr());
System.out.println("newStr实例对象值:" + fieldReflectDemo.getNewStr());
/**
- 输出
- constantStr原:FinalConstantStringField
- newStr原:FinalNewStringField
- constantStr反射修改:New Filed Name
- newStr反射修改:New Filed Name
- constantStr实例对象值:FinalConstantStringField
- newStr实例对象值:New Filed Name
*/
}
}因为JVM在编译时期, 就把final类型的直接赋值的String进行了优化, 在编译时期就会把String处理成常量。反射成功将其值修改成功了,但是在它的get方法中,返回的不是当前变量,而是返回JVM优化好的一个常量值。
Method类方法
获取方法类型的信息
方法声明包含了方法名、修饰符、参数、返回类型以及抛出的多个异常。
以及通过反射调用实例对象的方法。
1 | Class<? extends MethodReflectDemo> demoClass = methodReflectDemo.getClass(); |
Contructor类
获取
与Method相似,只是构造函数没有返回值,并且无法被实例对象执行,它的调用只能为给定的类创建对象的新实例
1 | Class<? extends ConstructorReflectDemo> demoClass = methodReflectDemo.getClass(); |
反射机制创建对象
- 先获取到这个有参数的构造方法【用ClassgetDeclaredConstructor()方法获取】
- 调用构造方法new对象【用Constructor类的newInstance()方法new对象】
1 | //通过反射机制调用构造方法实例化java对象。(这个不是重点) |
如果需要调用无参构造方法,getDeclaredConstructor()方法形参为空即可【和Class类的newInstance()方法一样的效果】!
万恶之源(题目)
题目要求
- 写⼀个关于复数类ComplexNumber的类描述,并完成编译。
1)属性: dRealPart:实部; dImaginPart:虚部
2)构造⽅法:ComplexNumber() 以及 ComplexNumber(double r, double i)
3)⽅法:复数相加 complexAdd(ComplexNumber c); 复数相减 complexMinus(ComplexNumber
c); 打印当前复数toString().
- 写⼀个新类,利⽤反射实现:
- 列出ComplexNumber类的构造函数、属性和⽅法。
- 调⽤复数相加的⽅法,显⽰出结果
代码
1 | import java.lang.reflect.Constructor; |
运行结果
1 | Constructors: |
写在最后
整理之前没有想到会这么多,懒狗一边划水一边整理,大概花了两天才写完,并且感觉大部分知识都是囫囵咽下的,姑且当个方法词典,随用随查吧。
有所纰漏,感谢指正