Zer0e's Blog

理解Java反射

字数统计: 1.6k阅读时长: 6 min
2020/03/22 Share

前言

这篇文章鸽了好久,拖稿了好几天,趁着有精力的时候多写写文章。Java语言一直被我认为是常见语言中最难的语言,主要是概念繁多,然后就是JVM的知识点复杂,导致尽管有些人会写代码,但是问到某个知识点却不知道为何是这样。
今天谈谈Java中的反射机制。什么是反射呢?反射就是在Java编译期以外的时间可以动态的加载一个类。还可以实例化对象,获取变量,方法等。

实践

入门

上面的概念刚开始我也不是很懂,为什么Java会有反射这种机制呢?假设有以下代码:

1
Cat cat = new Cat();

JVM编译时会把代码编译成class文件加载到jvm的内存中,以上代码会使Cat.java文件编译成Cat.class然后加载到JVM的内存当中,然后执行new申请内存空间,然后返回实例。这种方法只适合已经知道需要什么对象,而如果在运行的时候才确定了对象,此时已经错过了编译期,所以需要动态地加载一个类,这样的操作我们称作反射
那么反射有什么优点与缺点呢?优点就是十分灵活,类的加载无需重新编译,这种思想广泛用在了java生态当中,常见的框架都采用了反射的思想,如spring等。
而缺点就是影响性能,不建议使用在普通代码中,并且会使代码逻辑变得模糊,不利于维护。还有一点就是由于反射的功能强大,可以执行一些不被允许的操作(例如获取到类中的私有属性与方法),从而会引发某些问题。

Class类

在继续理解前,我们需要了解一下Java中的Class类。Class类是反射的基础。在Java中用来表示运行时类型信息的对应类就是Class类,存在于JDK的java.lang包中。手动编写一个类后,会产生一个Class对象,保存在同名的.class文件中。而这个类无论有多少个实例,内存中只有一个与之对应的Class对象。查看Class类的源码:

1
2
3
private Class(ClassLoader loader) {
classLoader = loader;
}

我们发现构造器为私有的,只有JVM能够创建Class对象,那么如何获得这个类的Class对象呢?有三种方法:

1
2
3
4
5
6
//1
Class c1 = Cat.class; //任何类都有隐含的静态成员class用于获取Class对象
//2
Class c2 = cat.getClass();//实例有一个getClass方法获取Class对象
//3
Class c3 = Class.forName("Cat");//Class类中的方法forName传入全量名可以获取这个类的Class对象

而通过Class对象获得类的信息的方法我们就称作反射(Reflection)。

反射的相关操作

获取成员方法

说了这么多,反射具体能做些啥呢?其实具体就以下几种:

  • 获取方法
  • 获取属性(变量)
  • 获取构造函数

这里我统一使用Class.forName()获取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
29
30
31
32
33
34
35
36
37
public class Cat {
public int id; //特意将id设为public,开发中不这么写
private String name;


public void setId(int id) {
this.id = id;
}

public void setName(String name) {
this.name = name;
}

public int getId() {
return id;
}

public String getName() {
return name;
}


private void hello(){
System.out.println(this.name);
}
}

public class Main {
public static void main(String[] args) throws Exception{
Class cls = Class.forName("model.Cat");
Object o = cls.newInstance();
Method method = cls.getMethod("setName", String.class);
method.invoke(o,"波斯猫");
System.out.println(cls.getMethod("getName").invoke(o));

}
}

首先使用Class.forName 获得Class对象,接着使用newInstance生成Cat的实例对象(之后会讲构造器的newInstance和直接的newInstance的区别),
使用getMethod可以获取某个方法,接着通过invoke方法传入对象与参数。
如果使用getMethods则可以获取所有方法。但是getMethod和getMethods获取的是public方法,如果需要获得私有方法,则使用getDeclaredMethod方法。

1
2
3
Method hello = cls.getDeclaredMethod("hello");
hello.setAccessible(true); //需要设置成可访问,才可以访问私有方法
hello.invoke(o);

获取成员属性

这里我们只写main方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public static void main(String[] args) throws Exception{

Class cls = Class.forName("model.Cat");
Object o = cls.newInstance();
Method method1 = cls.getMethod("setId", int.class);
method1.invoke(o,1);
Method method2 = cls.getMethod("setName", String.class);
method2.invoke(o,"哈士奇");

//获取成员属性
Field field = cls.getField("id");
System.out.println("id: " + field.get(o));

//获取成员private属性
Field field1 = cls.getDeclaredField("name");
field1.setAccessible(true);//依旧需要设置成可访问
System.out.println("name: " + field1.get(o));
}

获取构造函数

反射还可以获取类的构造函数。在这之前,有一个之前说的问题,Class的newInstance和构造器的newInstance有什么差。其实他们的区别在于Class的newInstance方法只能创建无参数构造函数的类的实例,而构造器的newInstance方法则可以创建带有参数的类的实例。
我们另写两个类

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
43
44
45
46
47
48
public class Dog {
private int id;
private String name;

public Dog(int id){
this.id = id;
}

public void setId(int id) {
this.id = id;
}

public void setName(String name) {
this.name = name;
}

public int getId() {
return id;
}

public String getName() {
return name;
}
}
public class Bird {
private int id;
private String name;

private Bird(int id){
this.id = id;
}

public void setId(int id) {
this.id = id;
}

public void setName(String name) {
this.name = name;
}

public int getId() {
return id;
}

public String getName() {
return name;
}
}

这两个类区别在于构造器分别是public和private。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class Main {

public static void main(String[] args) throws Exception{

Class cls = Class.forName("model.Dog");
Constructor constructor = cls.getConstructor(int.class);
Object o = constructor.newInstance(100);
Method method = cls.getMethod("getId");
System.out.println(method.invoke(o));


Class cls1 = Class.forName("model.Bird");
Constructor constructor1 = cls1.getDeclaredConstructor(int.class);
constructor1.setAccessible(true);
Object o1 = constructor1.newInstance(101);
System.out.println(cls1.getMethod("getId").invoke(o1));
}
}

获取构造器分别使用getConstructor与getDeclaredConstructor来获取公有和私有的构造器,而构造器的newInstance方法则是用来创建带参数的类的实例。

总结

这篇文章差不多讲了Java反射的入门知识,学习了Class类与反射的基本知识。不同于new,反射可以在需要的时候动态加载类。尽管在开发中反射并不常见,但了解反射的思想有助于对框架源码的解读。

参考

http://tengj.top/2016/04/28/javareflect/
https://www.cnblogs.com/chanshuyi/p/head_first_of_reflection.html

CATALOG
  1. 1. 前言
  2. 2. 实践
    1. 2.1. 入门
    2. 2.2. Class类
    3. 2.3. 反射的相关操作
      1. 2.3.1. 获取成员方法
      2. 2.3.2. 获取成员属性
      3. 2.3.3. 获取构造函数
  3. 3. 总结
  4. 4. 参考