JavaSE第8篇反射机制

基本概念

反射就是把Java类中的各个成分映射成一个个的Java对象。即在运行状态中,对于任意一个类,都能够知道这个类所有的属性和方法,对于任意一个对象,都能调用它的任意一个方法和属性。这种动态获取信息及动态调用对象方法的功能叫Java的反射机制

Java类加载机制
在Java程序启动时,JVM会将一部分类(class文件)先加载(并不是所有的类都会在一开始加载),通过ClassLoader将类加载,在加载过程中,会将类的信息提取出来(存放在元空间中,JDK1.8之前存放在永久代),同时也会生成一个Class对象存放在内存(堆内存),注意此Class对象只会存在一个,与加载的类唯一对应

Class类

Class类也是一个泛型类,只有第一种方法,能够直接获取到对应类型的Class对象。在JVM中每个类始终只存在一个Class对象,无论通过什么方法获取,都是一样的。现在我们再来看看这个问题

包装类型的Class对象并不是基本类型Class对象。数组类型也是一种类型,只是编程不可见,因此我们可以直接获取数组的Class对象

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public class Main {
public static void main(String[] args) throws InterruptedException, ClassNotFoundException {
//获取到每个类对应的Class对象呢
Class<String> clazz = String.class; //使用class关键字,通过类名获取
Class<?> clazz2 = Class.forName("java.lang.String"); //使用Class类静态方法forName(),通过包名.类名获取,注意返回值是Class<?>
Class<?> clazz3 = new String("cpdd").getClass();
System.out.println(clazz == clazz2);
System.out.println(clazz == clazz3);

//获取基本数据类型的Class对象
Class<?> clazz4 = int.class; //基本数据类型有Class对象吗?
System.out.println(clazz4);
System.out.println(Integer.TYPE == int.class);

//获取类的属性
Class<String[]> clazz5 = String[].class;
System.out.println(clazz5.getName()); //获取类名称(得到的是包名+类名的完整名称)
System.out.println(clazz5.getSimpleName());
System.out.println(clazz5.getTypeName());
System.out.println(clazz5.getClassLoader()); //获取它的类加载器
}
}

Class对象与多态

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
import java.lang.reflect.Type;

public class Main {
public static void main(String[] args) throws InterruptedException, ClassNotFoundException {
//类型判断的两种方法
String str = "";
System.out.println(str instanceof String);
System.out.println(str.getClass() == String.class); //直接判断是否为这个类型

//判断是否为子类或是接口/抽象类的实现
Integer numSub = 10;
System.out.println(numSub.getClass().asSubclass(Number.class));

//获取父类的原始类型的Type
Integer num = 10;
Type type = num.getClass().getGenericSuperclass();
System.out.println(type instanceof Class);

//获取父接口
Integer i = 10;
for (Class<?> anInterface : i.getClass().getInterfaces()) {
System.out.println(anInterface.getName());
}
for (Type genericInterface : i.getClass().getGenericInterfaces()) {
System.out.println(genericInterface.getTypeName());
}
}
}

创建类对象

拿到了类的定义,就可以通过Class对象来创建对象、调用方法、修改变量

默认无参构造

使用newInstance()且只适用于默认无参构造,也无法访问privite的构造方法

1
2
3
4
5
6
7
8
9
10
11
12
public class Main {
public static void main(String[] args) throws InstantiationException, IllegalAccessException {
Class<Student> clazz = Student.class;
Student student = clazz.newInstance();
student.show();
}
}
class Student {
public void show(){
System.out.println("萨日朗");
}
}

带参构造

通过获取类的构造方法(构造器)来创建对象实例,会更加合理,我们可以使用getConstructor()方法来获取类的构造方法,同时我们需要向其中填入参数,也就是构造方法需要的类型。

使用getDeclaredConstructor()方法可以找到类中的非public构造方法,但是在使用之前,我们需要先修改访问权限,在修改访问权限之后,就可以使用非public方法了(这意味着,反射可以无视权限修饰符访问类的内容)

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
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
public class Main {
public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {
Class<Student> clazz = Student.class;
Student student = clazz.getConstructor(String.class).newInstance("what's up");
student.show();

//获取非public的构造方法
Constructor<Student> constructor = clazz.getDeclaredConstructor(int.class);
constructor.setAccessible(true); //修改访问权限
Student studentPrivate = constructor.newInstance(12);
studentPrivate.mul();
}
}
class Student {
private String str;
private int num;
public Student(String str){
this.str = str;
}

private Student(int num){
this.num = num;
}

public void show(){
System.out.println(str);
}

public void mul(){
System.out.println(num * num);
}
}

调用类方法

通过调用getMethod()方法,我们可以获取到类中所有声明为public的方法,得到一个Method对象,我们可以通过Method对象的invoke()方法(返回值就是方法的返回值,因为这里是void,返回值为null)来调用已经获取到的方法,注意传参

利用反射之后,在一个对象从构造到方法调用,没有任何一处需要引用到对象的实际类型,我们也没有导入Student类,整个过程都是反射在代替进行操作,使得整个过程被模糊了,过多的使用反射,会极大地降低后期维护性

同构造方法一样,当出现非public方法时,我们可以通过反射来无视权限修饰符,获取非public方法并调用

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
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Arrays;

public class Main {
public static void main(String[] args) throws ClassNotFoundException, InstantiationException, IllegalAccessException, NoSuchMethodException, InvocationTargetException {
//获取public的方法
Class<?> clazz = Class.forName("Student");
Object instance = clazz.newInstance(); //创建出学生对象
Method method = clazz.getMethod("show", String.class); //通过方法名和形参类型获取类中的方法
method.invoke(instance, "what's up"); //通过Method对象的invoke方法来调用方法

//获取private的方法
Method methodPri = clazz.getDeclaredMethod("show", Integer.class); //通过方法名和形参类型获取类中的方法
methodPri.setAccessible(true);
methodPri.invoke(instance, 8); //通过Method对象的invoke方法来调用方法

//获取方法相关属性
System.out.println(method.getName()); //获取方法名称
System.out.println(method.getReturnType()); //获取返回值类型

//参数为可变参数时
Method methodVariable = clazz.getDeclaredMethod("show", int[].class); //可变参数的获取
methodVariable.invoke(instance,new int[]{1,2,3});
}
}
class Student {
public void show(String str){
System.out.println(str);
}

private void show(Integer num){
System.out.println(num * num);
}

public void show(int ...num) {
System.out.println(Arrays.stream(num).count());
}
}

修改类的属性

我们还可以通过反射访问一个类中定义的成员字段也可以修改一个类的对象中的成员字段值,通过getField()方法来获取一个类定义的指定字段。在得到Field之后,我们就可以直接通过set()方法为某个对象,设定此属性的值

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
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

public class Main {
public static void main(String[] args) throws ClassNotFoundException, InstantiationException, IllegalAccessException, NoSuchMethodException, InvocationTargetException, NoSuchFieldException {
Class<?> clazz = Class.forName("Student");
Object instance = clazz.newInstance(); //创建出学生对象
Field field = clazz.getField("i"); //获取类的成员字段i
field.set(instance, 100); //将类实例instance的成员字段i设置为100
Method method = clazz.getMethod("show");
method.invoke(instance);

//获取私有成员字段
Field fieldPri = clazz.getDeclaredField("j");
fieldPri.setAccessible(true);
fieldPri.set(instance, 200); //将类实例instance的成员字段i设置为100
method.invoke(instance);
}
}

class Student {
public int i;
private int j;
public void show() {
System.out.println("i=" + i + ",j=" + j);
}
}

JavaSE第8篇反射机制
https://www.eldpepar.com/coding/26734/
作者
EldPepar
发布于
2022年10月23日
许可协议