反射被视为动态语言的关键。Java的反射(reflection)机制是指在程序的运行状态中,可以构造任意一个类的对象,可以了解任意一个对象所属的类,可以了解任意一个类的成员变量和方法,可以调用任意一个对象的属性和方法。这种动态获取程序信息以及动态调用对象的功能称为Java语言的反射机制。

反射的定义

在Java中反射(Reflection)是一种Java 程序运行期间的动态技术,它可以在运行时(runtime)检査、修改其自身结构或行为。通过反射,程序可以访问、检测和修改它自己的类、对象、方法、属性等成员。即在运行状态中,对于任意一个类,都能够知道这个类的所以属性和方法;对于任意一个对象,都能调用它的任意一个方法和属性。这种动态获取信息及动态调用对象方法的功能叫Java的反射机制。

反射的作用

  1. 动态加载类:程序可以在运行时动态地加载类库中的类;
  2. 动态创建对象:反射可以基于类的信息,程序可以在运行时,动态创建对象实例;
  3. 调用方法:反射可以根据方法名称,程序可以在运行时,动态地调用对象的方法(即使方法在编写程序时还没有定义)
  4. 访问成员变量:反射可以根据成员变量名称,程序可以在运行时,访问和修改成员变量(反射可以访问私有成员变量)
  5. 运行时类型信息:反射允许程序在运行时,查询对象的类型信息,这对于编写通用的代码和库非常有用;

实现反射机制的类

Java中主要由以下的类来实现Java反射机制(这些类都位于java.lang.reflect包中):

  1. Class类:代表一个类。 Field类:代表类的成员变量(成员变量也称为类的属性)。
  2. Method类:代表类的方法。
  3. Constructor类:代表类的构造方法。
  4. Array类:提供了动态创建数组,以及访问数组的元素的静态方法。

反射的基础使用

获取Class对象

反射的第一步是获取 Class 对象(动态加载类)。Class 对象表示某个类的元数据,可以通过以下几种方式获取:

1
2
3
4
5
6
7
8
//方式1:通过类名
Class stringClass1 = String.class;

//方式2:通过Class类的forName()方法
Class stringClass2 = Class.forName("java.lang.String");

//方式3:通过对象调用getClass()方法
Class stringClass3 = "".getClass();

解析字符串为Java对象

使用fastjsonJSON类解析一个字符串为Java对象,并输出该对象的字段值。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import com.alibaba.fastjson.JSON;
import lombok.Data;

public class ReflectionTest {
public static void main(String[] args) {
String demoStr = "{\"age\":18,\"name\":\"demo1\"}";
Demo demo = JSON.parseObject(demoStr, Demo.class);
System.out.printf("name is : %s%n", demo.getName());
System.out.printf("age is : %s%n", demo.getAge());
}
}

@Data
class Demo {
private String name;
private Integer age;
}

输出

1
2
name is : demo1
age is : 18

获取类的相关信息

通过 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
import java.lang.reflect.*;

public class ReflectionTest2 {
public static void main(String[] args) throws ClassNotFoundException {
Class clz = Class.forName("java.util.HashMap");

//获取类名
System.out.println("完全限定名:" + clz.getName());
System.out.println("简单的类名:" + clz.getSimpleName());

//获取包名
System.out.println("package" + clz.getPackage().getName());
System.out.println();

//获取成员变量
Field[] fieldArray = clz.getDeclaredFields();
System.out.println("成员变量(字段)");
for (Field field : fieldArray) {
System.out.println(field);
}
System.out.println();

//获取成员方法
Method[] methodArray = clz.getDeclaredMethods();
System.out.println("成员方法");
for (Method method : methodArray) {
System.out.println(method);
}
}
}

通过反射创建对象

通过反身创建对象,通过反射获取方法,通过反射执行方法等。

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
import lombok.SneakyThrows;
import java.lang.reflect.Method;

public class ReflectionTest3 {
@SneakyThrows
public static void main(String[] args) {
//获取class对象
Class<Demo> clazz = Demo.class;
//获取Demo class 的setName 方法
Method setName = clazz.getMethod("setName", String.class);
//获取Demo class 的实例
Demo demo = clazz.newInstance();
//在实例化后的对象demo执行setName方法
setName.invoke(demo, "new Name");
//获取name并输出
System.out.printf("demo name is : %s%n", demo.getName());
//通过反射在demo对象执行getName
Method getName = clazz.getMethod("getName");
System.out.printf("demo reflect getname is : %s%n", getName.invoke(demo));

//通过反射在其它demo对象上执行getName
Demo demo2 = new Demo();
demo2.setName("demo2");
System.out.printf("demo2 reflect getname is : %s%n", getName.invoke(demo2));
}
}

输出

1
2
3
demo name is : new Name
demo reflect getname is : new Name
demo2 reflect getname is : demo2

反射的性能问题

反射虽然功能强大,但由于是在运行时动态操作类,因此性能相对较低。此外,反射也会破坏封装性,使用时要谨慎。

反射的安全性

使用反射时需要注意安全问题,因为它可以绕过Java的访问控制机制。例如,可以访问私有字段或方法,因此在开发中使用反射要特别小心。甚至在运行时,添加、改变方法。

反射的常见场景

  1. 框架开发:如 Spring 中的依赖注入、Hibernate 中的 ORM 等。
  2. 调试工具:如 Java 的调试器、分析工具等。
  3. 动态代理:在 Java 中,动态代理依赖于反射。
  4. 常用的fastjson JSONObject.parseObject(xxStr)