简单了解Java反射

1. 什么是 Java 反射?

  • 核心概念: 反射是指在 运行时 检查、访问和修改类、接口、字段和方法的能力。 简单来说,就是你可以在程序运行的时候,动态地获取一个类的各种信息,并操作这个类。
  • 为什么要用反射?
    • 动态性: 可以在运行时创建对象、调用方法,而不需要在编译时知道类的具体信息。 这使得程序更加灵活和可扩展。
    • 通用性: 可以编写通用的代码来处理不同类型的对象,而不需要为每种类型编写特定的代码。
    • 框架和工具的基础: 许多 Java 框架和工具(如 Spring、Hibernate、JUnit)都大量使用了反射。

2. 反射能做什么?

通过反射,我们可以:

  • 获取类的 Class 对象: Class 对象是 Java 反射的入口点,通过 Class 对象可以获取类的各种信息。
  • 创建类的实例: 可以通过 Class 对象创建类的实例。
  • 获取类的构造方法: 可以获取类的所有构造方法,包括公有、私有和受保护的。
  • 调用类的构造方法: 可以通过构造方法创建类的实例。
  • 获取类的字段: 可以获取类的所有字段,包括公有、私有和受保护的。
  • 访问和修改字段的值: 可以读取和修改字段的值,即使是私有字段。
  • 获取类的方法: 可以获取类的所有方法,包括公有、私有和受保护的。
  • 调用类的方法: 可以调用类的方法,即使是私有方法。
  • 获取类的注解: 可以获取类、字段和方法上的注解。

3. 反射的核心类和接口

Java 反射主要涉及以下几个核心类和接口:

  • java.lang.Class 代表一个类或接口。 这是反射的入口点。
  • java.lang.reflect.Constructor 代表一个类的构造方法。
  • java.lang.reflect.Field 代表一个类的字段。
  • java.lang.reflect.Method 代表一个类的方法。
  • java.lang.reflect.Modifier 提供关于类和成员访问修饰符的信息。
  • java.lang.reflect.InvocationTargetException 当反射调用的方法抛出异常时,会抛出此异常。

4. 获取 Class 对象

要使用反射,首先需要获取 Class 对象。 有以下几种方式可以获取 Class 对象:

  • Class.forName(String className) 通过类的完全限定名获取 Class 对象。 这是最常用的方式。
1
2
3
4
5
6
try {
Class<?> myClass = Class.forName("com.example.MyClass");
System.out.println("Class name: " + myClass.getName());
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
  • 类名.class 如果已经知道类的名称,可以使用这种方式。
1
2
Class<?> myClass = MyClass.class;
System.out.println("Class name: " + myClass.getName());
  • 对象.getClass() 如果已经有类的实例,可以使用这种方式。
1
2
3
MyClass obj = new MyClass();
Class<?> myClass = obj.getClass();
System.out.println("Class name: " + myClass.getName());

5. 创建类的实例

可以通过 Class 对象的 newInstance() 方法创建类的实例(要求类必须有无参构造方法)。

1
2
3
4
5
6
7
try {
Class<?> myClass = Class.forName("com.example.MyClass");
Object obj = myClass.newInstance();
System.out.println("Object created: " + obj);
} catch (ClassNotFoundException | InstantiationException | IllegalAccessException e) {
e.printStackTrace();
}

如果类没有无参构造方法,或者想使用带参数的构造方法,需要先获取 Constructor 对象,然后调用 newInstance() 方法。

1
2
3
4
5
6
7
8
try {
Class<?> myClass = Class.forName("com.example.MyClass");
Constructor<?> constructor = myClass.getConstructor(String.class, int.class); // 获取带 String 和 int 参数的构造方法
Object obj = constructor.newInstance("Hello", 123); // 调用带参数的构造方法
System.out.println("Object created: " + obj);
} catch (ClassNotFoundException | NoSuchMethodException | InstantiationException | IllegalAccessException | InvocationTargetException e) {
e.printStackTrace();
}

6. 获取和访问字段

可以通过 Class 对象的 getField()getDeclaredField() 方法获取字段。

  • getField(String name) 获取公有字段。
  • getDeclaredField(String name) 获取所有字段,包括公有、私有和受保护的。
1
2
3
4
5
6
7
8
9
10
11
try {
Class<?> myClass = Class.forName("com.example.MyClass");
Field myField = myClass.getDeclaredField("myPrivateField"); // 获取名为 myPrivateField 的字段
myField.setAccessible(true); // 设置为可访问,即使是私有字段
MyClass obj = new MyClass();
myField.set(obj, "New value"); // 设置字段的值
String value = (String) myField.get(obj); // 获取字段的值
System.out.println("Field value: " + value);
} catch (ClassNotFoundException | NoSuchFieldException | IllegalAccessException e) {
e.printStackTrace();
}

7. 获取和调用方法

可以通过 Class 对象的 getMethod()getDeclaredMethod() 方法获取方法。

  • getMethod(String name, Class<?>... parameterTypes) 获取公有方法。
  • getDeclaredMethod(String name, Class<?>... parameterTypes) 获取所有方法,包括公有、私有和受保护的。
1
2
3
4
5
6
7
8
9
10
try {
Class<?> myClass = Class.forName("com.example.MyClass");
Method myMethod = myClass.getDeclaredMethod("myPrivateMethod", String.class); // 获取名为 myPrivateMethod 的方法,参数类型为 String
myMethod.setAccessible(true); // 设置为可访问,即使是私有方法
MyClass obj = new MyClass();
String result = (String) myMethod.invoke(obj, "World"); // 调用方法
System.out.println("Method result: " + result);
} catch (ClassNotFoundException | NoSuchMethodException | IllegalAccessException | InvocationTargetException e) {
e.printStackTrace();
}

8. 代码示例:完整的反射示例

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
49
50
51
52
53
54
package com.example;

import java.lang.reflect.*;

public class ReflectionExample {
public static void main(String[] args) {
try {
// 1. 获取 Class 对象
Class<?> myClass = Class.forName("com.example.MyClass");
System.out.println("Class name: " + myClass.getName());

// 2. 创建实例
Constructor<?> constructor = myClass.getConstructor(String.class, int.class);
Object obj = constructor.newInstance("Hello", 123);
System.out.println("Object created: " + obj);

// 3. 获取和访问字段
Field myField = myClass.getDeclaredField("myPrivateField");
myField.setAccessible(true);
myField.set(obj, "New value");
String value = (String) myField.get(obj);
System.out.println("Field value: " + value);

// 4. 获取和调用方法
Method myMethod = myClass.getDeclaredMethod("myPrivateMethod", String.class);
myMethod.setAccessible(true);
String result = (String) myMethod.invoke(obj, "World");
System.out.println("Method result: " + result);

} catch (ClassNotFoundException | NoSuchMethodException | InstantiationException | IllegalAccessException | InvocationTargetException | NoSuchFieldException e) {
e.printStackTrace();
}
}
}

class MyClass {
private String myPrivateField = "Original value";
public int myPublicField = 0;

public MyClass() {
}

public MyClass(String str, int i) {
System.out.println("MyClass constructor with parameters called: " + str + ", " + i);
}

private String myPrivateMethod(String arg) {
return "Hello " + arg + ", myPrivateField = " + myPrivateField;
}

public void myPublicMethod() {
System.out.println("myPublicMethod called");
}
}

9. 反射的优缺点

  • 优点:

    • 动态性: 允许在运行时动态地加载类、创建对象、调用方法。
    • 通用性: 可以编写通用的代码来处理不同类型的对象。
    • 可扩展性: 方便扩展应用程序,而不需要修改现有的代码。
  • 缺点:

    • 性能开销: 反射操作通常比直接代码执行慢,因为它涉及到运行时的类型检查和方法调用。
    • 安全问题: 反射可以访问和修改私有成员,这可能会破坏类的封装性,导致安全问题。
    • 可读性差: 反射代码通常比较复杂,难以阅读和维护。
    • 异常处理: 反射操作需要处理很多异常,例如 ClassNotFoundExceptionNoSuchMethodExceptionIllegalAccessException 等。

10. 总结

Java 反射是一种强大的机制,它允许在运行时检查、访问和修改类、接口、字段和方法。 虽然反射提供了很大的灵活性,但也需要注意其性能开销、安全问题和可读性问题。 在使用反射时,需要权衡其优缺点,并根据实际情况进行选择。