1. Java 反射的原理
在 Java 中,每个类在编译后都会生成一个 .class
文件,JVM 会为每个加载的类创建一个 Class
对象,这个对象包含了类的全部结构信息,包括类名、方法、字段、构造函数等。Class
对象存储了类的元数据,这些元数据可以在运行时被访问。通过 Class
对象,程序可以动态地获取类的信息和操作类的内容。反射的核心在于通过 Class
对象动态加载类,获取类的各种结构信息。
Java 反射机制基于以下几种核心类:
Class
:表示类的对象,提供了类的名称、字段、方法等信息。Field
:表示类的成员变量,提供了访问和修改字段值的方法。Method
:表示类的方法,提供了调用方法的功能。Constructor
:表示类的构造函数,提供了创建实例的方法。
这些类都位于 java.lang.reflect
包中。
2. 获取类的Class对象
2.1 Class.forName()
Class<?> classForName = Class.forName("java.lang.String");
System.out.println("Class.forName: " + classForName.getName()); // 输出 java.lang.String
解释:
Class.forName()
是一种动态加载类的方法,通过类的全限定名(包名+类名)来获取类的 Class
对象。
2.2 .class
Class<?> _class = String.class;
System.out.println(".class: " + _class.getName()); // 输出 java.lang.String
解释:
.class
是编译时获取 Class
对象的方式,JVM 在编译期就知道该类,并为其生成相应的 Class
对象。这是静态类型安全的方式。
2.3 getClass()
String sayHello = "Hello World";
Class<?> classForInstance = sayHello.getClass();
System.out.println("getClass(): " + classForInstance.getName()); // 输出 java.lang.String
解释:getClass()
是通过对象实例来获取该实例所属类的 Class
对象。这种方式适用于已经有了对象实例的情况。
2.4 ClassLoader
ClassLoader classLoader = ClassReflectDemo.class.getClassLoader();
Class<?> classForLoader = classLoader.loadClass("java.lang.Integer");
System.out.println("ClassLoader: " + classForLoader.getName()); // 输出 java.lang.Integer
解释:每个类都是由类加载器加载的,ClassLoader
提供了一种通过类加载器加载类并返回其 Class
对象的方式。这是一种更灵活的方式,可以自定义类加载器。
2.5 通过子类对象获取父类 Class对象
Class<?> superClass = String.class.getSuperclass();
System.out.println("Superclass: " + superClass.getName()); // 输出 java.lang.Object
解释:每个对象不仅知道它自己的类,还知道它的父类。通过 getSuperclass()
方法,可以获取该类的直接父类的 Class
对象。
2.6 通过接口类型
Class<?> interfaceClass = String.class.getInterfaces()[0];
System.out.println("Interface Class: " + interfaceClass.getName()); // 输出 java.io.Serializable
解释:对象不仅属于某个类,还可能实现一个或多个接口。通过 getInterfaces()
方法,可以获取一个对象实现的接口的 Class
对象。
2.7 通过注解
Annotation annotation = A_Class.class.getAnnotation(Name.class);
System.out.println("Annotation: " + annotation); // 输出 @jdk.jfr.Name(value="123")
解释:类可以被注解标记,通过 getAnnotation()
获取注解实例,再通过 annotationType()
获取注解的 Class
对象。
2.8 使用动态代理
InvocationHandler handler = (_, _, _) -> null; // 使用 Lambda 表达式简化实现,不做任何处理。
Runnable proxyInstance = (Runnable) Proxy.newProxyInstance(
Runnable.class.getClassLoader(),
new Class[]{Runnable.class},
handler
);
Class<?> proxyClass = proxyInstance.getClass();
System.out.println("Proxy Class: " + proxyClass.getName()); // 输出类似 jdk.proxy1.$Proxy0 的类名
proxyInstance.run();
解释:动态代理允许在运行时创建代理类。通过 Proxy.newProxyInstance()
可以创建代理实例,代理实例有自己的 Class
对象。
2.9 通过泛型类型
B_Class<String> bClass = new B_Class<>(){};
Class<?> genericClass = bClass.getGenericTypeClass();
System.out.println("Generic Class: " + genericClass.getName()); // 输出 java.lang.String
解释:泛型在编译时会被类型擦除,但可以通过 ParameterizedType
获取泛型的实际类型,并转换为 Class
对象,由于 Java 泛型的擦除机制,直接实例化 B_Class<String>
时,T
的实际类型在运行时丢失,导致 getGenericTypeClass()
方法返回 Object.class
。通过使用匿名子类或显式子类,可以保留泛型类型信息,从而正确获取泛型的实际类型。
案例完整代码:
import jdk.jfr.Name;
import java.lang.annotation.Annotation;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Proxy;
import java.lang.reflect.Type;
public class ClassReflectDemo {
public static void main(String[] args) {
try {
// 1. 使用 Class.forName() 动态加载类
Class<?> classForName = Class.forName("java.lang.String");
System.out.println("Class.forName: " + classForName.getName());// 输出 java.lang.String
// 2. 使用 .class 语法获取类对象
Class<?> _class = String.class;
System.out.println(".class: " + _class.getName()); // 输出 java.lang.String
// 3. 使用 getClass() 通过实例获取类对象
String sayHello = "Hello World";
Class<?> classForInstance = sayHello.getClass();
System.out.println("getClass(): " + classForInstance.getName()); // 输出 java.lang.String
// 4. 使用 ClassLoader 加载类
ClassLoader classLoader = ClassReflectDemo.class.getClassLoader();
Class<?> classForLoader = classLoader.loadClass("java.lang.Integer");
System.out.println("ClassLoader: " + classForLoader.getName()); // 输出 java.lang.Integer
// 5. 获取类的父类
Class<?> superClass = String.class.getSuperclass();
System.out.println("Superclass: " + superClass.getName()); // 输出 java.lang.Object
// 6. 获取类实现的接口
Class<?> interfaceClass = String.class.getInterfaces()[0];
System.out.println("Interface Class: " + interfaceClass.getName()); // 输出 java.io.Serializable
// 7. 通过匿名子类保留泛型类型信息并获取泛型参数的 Class 对象
B_Class<String> bClass = new B_Class<>() {
};
Class<?> genericClass = bClass.getGenericTypeClass();
System.out.println("Generic Class: " + genericClass.getName()); // 输出 java.lang.String
// 8. 获取类上的注解并输出
Annotation annotation = A_Class.class.getAnnotation(Name.class);
System.out.println("Annotation: " + annotation); // 输出 @jdk.jfr.Name(value="123")
// 9. 使用匿名内部类创建 InvocationHandler
InvocationHandler handler = (_, _, _) -> null; // 使用 Lambda 表达式简化实现,不做任何处理。
// 10. 使用动态代理创建一个实现 Runnable 接口的代理对象
Runnable proxyInstance = (Runnable) Proxy.newProxyInstance(
Runnable.class.getClassLoader(),
new Class[]{Runnable.class},
handler
);
// 11. 获取代理对象的类名
Class<?> proxyClass = proxyInstance.getClass();
System.out.println("Proxy Class: " + proxyClass.getName()); // 输出类似 jdk.proxy1.$Proxy0 的类名
// 12. 调用代理对象的方法
proxyInstance.run(); // 由于 handler 不处理方法调用,因此这里不会有输出或实际操作。
} catch (ClassNotFoundException e) {
throw new RuntimeException(e);
}
}
}
// 使用 @Name 注解标记 A_Class 类,Name 注解的值为 "123"
@Name("123")
class A_Class {
}
// 泛型类 B_Class,用于演示如何通过反射获取泛型参数的类型
class B_Class<T> {
public Class<?> getGenericTypeClass() {
// 获取当前类的直接超类的类型信息
Type superclass = getClass().getGenericSuperclass();
// 检查是否为 ParameterizedType,即泛型类型
if (superclass instanceof ParameterizedType) {
// 获取实际的泛型类型参数
Type[] actualTypeArguments = ((ParameterizedType) superclass).getActualTypeArguments();
// 返回泛型参数的 Class 对象,如果存在泛型参数
if (actualTypeArguments != null && actualTypeArguments.length > 0) {
return (Class<?>) actualTypeArguments[0];
}
}
// 如果无法确定泛型类型,默认返回 Object.class
return Object.class;
}
}
运行结果:
Class.forName: java.lang.String
.class: java.lang.String
getClass(): java.lang.String
ClassLoader: java.lang.Integer
Superclass: java.lang.Object
Interface Class: java.io.Serializable
Generic Class: java.lang.String
Annotation: @jdk.jfr.Name(“123”)
Proxy Class: jdk.proxy1.$Proxy2
小结:反射机制可以操作几乎所有的 Java 对象,包括普通对象、数组、枚举、匿名类等。
3. 通过反射创建实例
3.1 Class.newInstance()(已废弃)
class A_Class {
public A_Class() {
System.out.println("调用了无参构造!");
}
}
A_Class.class.newInstance(); // 等同于 new A_Class(); 输出 "调用了无参构造!"
解释:Class.newInstance()
方法调用无参构造函数来创建实例。但自版本 9 起已弃用 ,原因是它无法处理带有私有构造函数的类,并且对异常的处理方式不够灵活。推荐使用 Constructor.newInstance()
方法。
3.2 Constructor.newInstance()
Constructor<?> constructor = String.class.getConstructor(String.class);
Object constructorInstance = constructor.newInstance("Hello World");
System.out.println("Constructor instance:" + constructorInstance); // 输出 "Constructor instance:Hello World"
解释:通过获取 Constructor
对象并调用 newInstance()
方法,可以使用指定的构造函数创建实例。这个方法比 Class.newInstance()
更加灵活,支持带参数的构造函数,并且可以处理私有构造函数。
3.3 使用私有构造函数创建实例
class B_Class {
private B_Class() {
System.out.println("调用了私有无参构造!");
}
private B_Class(String name, Integer age) {
System.out.println("私有有参构造:" + name + "今年" + age + "岁");
}
}
Constructor<?> bClassConstructor1 = B_Class.class.getDeclaredConstructor();
bClassConstructor1.setAccessible(true); // 绕过访问控制检查
bClassConstructor1.newInstance(); // 输出 "调用了私有无参构造!"
Constructor<?> bClassConstructor2 = B_Class.class.getDeclaredConstructor(String.class, Integer.class);
bClassConstructor2.setAccessible(true); // 绕过访问控制检查
bClassConstructor2.newInstance("张三", 20); // 输出 "私有有参构造:张三今年20岁"
解释:通过 setAccessible(true)
,可以访问并使用私有构造函数来创建实例。getDeclaredConstructor(Class<?>... parameterTypes)
: 通过指定参数类型列表获取对应的构造函数。这里 String.class
和 Integer.class
对应于构造函数的参数类型。
3.4 使用数组类的反射创建实例
Class<?> clazz = int[].class;
Object arrayInstance = java.lang.reflect.Array.newInstance(clazz.getComponentType(), 5);
System.out.println(java.util.Arrays.toString((int[]) arrayInstance)); // 输出 "[0, 0, 0, 0, 0]"
解释:使用 java.lang.reflect.Array.newInstance()
可以创建数组实例。
clazz.getComponentType()
:这个方法返回数组的组件类型,即 int[].class
的组件类型是 int.class
。这是为了告诉反射我们要创建的数组的元素类型是什么。
Array.newInstance(Class<?> componentType, int length)
:Array
类是 Java 反射包中用于操作数组的类。newInstance
方法用于创建一个新数组,componentType
参数指定数组的元素类型,length
参数指定数组的长度。这里我们创建了一个长度为 5 的 int
数组。
arrayInstance
:这是通过反射创建的数组对象,类型为 Object
。但实际上它是一个 int[]
类型的数组。
3.5 通过反序列化创建实例
class C_Class implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
public C_Class() {
System.out.println("反序列化创建实例");
}
}
String filename = "example.ser";
File file = new File(filename);
if (file.exists()) {
// 文件存在,直接反序列化
try (ObjectInputStream ois = new ObjectInputStream(new FileInputStream(file))) {
C_Class instance = (C_Class) ois.readObject(); // 从文件中读取并反序列化对象
System.out.println("反序列化对象: " + instance); // 输出 "反序列化对象: C_Class@hashcode"
} catch (IOException | ClassNotFoundException e) {
e.printStackTrace();
}
} else {
// 文件不存在,先序列化对象到文件
try (ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(file))) {
C_Class example = new C_Class(); // 输出 "反序列化创建实例"
oos.writeObject(example); // 将对象写入文件
System.out.println("对象已序列化到文件 " + filename);
} catch (IOException e) {
e.printStackTrace();
}
}
解释:通过反序列化,将已经序列化的对象从字节流恢复为一个对象实例。如果文件存在,则执行反序列化操作,从文件中读取对象。如果文件不存在,则执行序列化操作,将对象保存到文件中。
3.6 使用工厂方法创建实例
class D_Class {
public static D_Class createInstance() {
System.out.println("通过工厂创建方法实例");
return new D_Class();
}
}
Method method = D_Class.class.getMethod("createInstance");
method.invoke(null); // 输出 "通过工厂创建方法实例"
解释:通过反射调用类的工厂方法来创建实例。
案例完整代码:
import java.io.*;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
public class ClassReflectDemo {
public static void main(String[] args) {
try {
// 1. 通过反射调用无参构造函数创建 A_Class 的实例
A_Class.class.newInstance(); // 等同于 new A_Class(); 输出 "调用了无参构造!"
// 2. 通过反射调用 String 类的带参构造函数创建实例
Constructor<?> constructor = String.class.getConstructor(String.class);
Object constructorInstance = constructor.newInstance("Hello World");
System.out.println("Constructor instance:" + constructorInstance); // 输出 "Constructor instance:Hello World"
// 3. 通过反射调用 B_Class 的私有无参构造函数
Constructor<?> bClassConstructor1 = B_Class.class.getDeclaredConstructor();
bClassConstructor1.setAccessible(true); // 绕过访问控制检查
bClassConstructor1.newInstance(); // 输出 "调用了私有无参构造!"
// 4. 通过反射调用 B_Class 的私有带参构造函数
Constructor<?> bClassConstructor2 = B_Class.class.getDeclaredConstructor(String.class, Integer.class);
bClassConstructor2.setAccessible(true); // 绕过访问控制检查
bClassConstructor2.newInstance("张三", 20); // 输出 "私有有参构造:张三今年20岁"
// 5. 通过反射创建一个 int 数组实例,并初始化为长度为 5 的数组
Class<?> clazz = int[].class;
Object arrayInstance = java.lang.reflect.Array.newInstance(clazz.getComponentType(), 5);
System.out.println(java.util.Arrays.toString((int[]) arrayInstance)); // 输出 "[0, 0, 0, 0, 0]"
// 6. 判断文件是否存在,如果存在则反序列化 C_Class 对象,否则先序列化再反序列化
String filename = "example.ser";
File file = new File(filename);
if (file.exists()) {
// 文件存在,直接反序列化
try (ObjectInputStream ois = new ObjectInputStream(new FileInputStream(file))) {
C_Class instance = (C_Class) ois.readObject(); // 从文件中读取并反序列化对象
System.out.println("反序列化对象: " + instance); // 输出 "反序列化对象: C_Class@hashcode"
} catch (IOException | ClassNotFoundException e) {
e.printStackTrace();
}
} else {
// 文件不存在,先序列化对象到文件
try (ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(file))) {
C_Class example = new C_Class(); // 输出 "反序列化创建实例"
oos.writeObject(example); // 将对象写入文件
System.out.println("对象已序列化到文件 " + filename);
} catch (IOException e) {
e.printStackTrace();
}
}
// 7. 通过反射调用 D_Class 的静态工厂方法创建实例
Method method = D_Class.class.getMethod("createInstance");
method.invoke(null); // 输出 "通过工厂创建方法实例"
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}
class A_Class {
public A_Class() {
System.out.println("调用了无参构造!");
}
}
class B_Class {
private B_Class() {
System.out.println("调用了私有无参构造!");
}
private B_Class(String name, Integer age) {
System.out.println("私有有参构造:" + name + "今年" + age + "岁");
}
}
class C_Class implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
public C_Class() {
System.out.println("反序列化创建实例");
}
}
class D_Class {
public static D_Class createInstance() {
System.out.println("通过工厂创建方法实例");
return new D_Class();
}
}
运行结果:
调用了无参构造!
Constructor instance:Hello World
调用了私有无参构造!
私有有参构造:张三今年20岁
[0, 0, 0, 0, 0]
反序列化对象: C_Class@46f7f36a
通过工厂创建方法实例
4. 访问私有字段
访问私有字段通常通过公共的getter和setter方法实现,但在某些情况下,你可能需要使用反射直接访问和修改私有字段。使用反射可以绕过Java的访问控制机制,直接操作私有字段。
案例完整代码:
import java.lang.reflect.Field;
public class ClassReflectDemo {
public static void main(String[] args) {
try {
// 1. 创建 A_Class 的实例
A_Class instance = new A_Class();
// 2. 获取 A_Class 的 Class 对象
Class<?> aClass = A_Class.class;
// 3. 获取私有字段 'name'
Field nameField = aClass.getDeclaredField("name");
// getDeclaredField(String name) 返回一个 Field 对象,表示类中声明的名为 'name' 的字段。
// 这个字段是私有的,需要后续步骤来访问它。
// 4. 绕过访问控制
nameField.setAccessible(true);
// setAccessible(true) 是关键一步,它绕过了 Java 的访问控制机制,使得可以访问和修改私有字段。
// 5. 获取私有字段 'name' 的值
Object name = nameField.get(instance);
// 使用 Field 对象的 get() 方法来读取指定实例的字段值。
// 这里读取了 'name' 字段的值,即 "joke"。
// 6. 打印读取到的私有字段的值
System.out.println("读取初始化值:"+name); // 输出: 读取初始化值:joke
// 7. 修改私有字段 'name' 的值
nameField.set(instance, "jokes");
// 使用 Field 对象的 set() 方法来修改指定实例的字段值。
// 这里将 'name' 字段的值从 "joke" 修改为 "jokes"。
// 8. 再次读取并打印私有字段 'name' 的值
name = nameField.get(instance);
System.out.println("读取修改后的值:"+name); // 输出: 读取修改后的值:jokes
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}
class A_Class {
private String name = "joke";
}
运行结果:
读取初始化值:joke
读取修改后的值:jokes
解释:通过 getDeclaredField()
方法获取私有字段,使用 setAccessible(true)
绕过访问控制,然后通过 get()
和 set()
方法读取和修改字段值。
5. 调用方法
案例完整代码:
import java.lang.reflect.Method;
public class ClassReflectDemo {
public static void main(String[] args) {
try {
// 1. 创建 A_Class 的实例
A_Class instance = new A_Class();
// 2. 获取公共方法 'publicMethod' 的 Method 对象
Method publicMethod = A_Class.class.getMethod("publicMethod");
// 3. 调用公共方法
publicMethod.invoke(instance);
// 4. 获取私有方法 'privateMethod' 的 Method 对象
Method privateMethod = A_Class.class.getDeclaredMethod("privateMethod");
// 5. 设置方法可访问
privateMethod.setAccessible(true);
// 6. 调用私有方法
privateMethod.invoke(instance);
// 7. 获取带参数的公共方法 'methodWithArgs'
Method methodWithArgs = A_Class.class.getMethod("methodWithArgs", String.class, Integer.class);
// 8. 调用带参数的方法
methodWithArgs.invoke(instance, "Joke", 42);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}
class A_Class {
// 公共方法
public void publicMethod() {
System.out.println("调用了公共方法!");
}
// 私有方法
private void privateMethod() {
System.out.println("调用了私有方法!");
}
// 带参数的公共方法
public void methodWithArgs(String message, Integer number) {
System.out.println("调用了带参数的方法:" + message + ", " + number);
}
}
运行结果:
调用了公共方法!
调用了私有方法!
调用了带参数的方法:Joke, 42
解释:
使用反射获取 Method
对象,这个对象表示要调用的方法。可以使用 getMethod()
或 getDeclaredMethod()
获取 Method
对象。如果方法是私有的,需要通过 setAccessible(true)
来绕过访问控制。使用 Method.invoke(Object obj, Object... args)
来调用方法。obj
是要调用方法的对象,args
是传递给方法的参数。
6.通配符?
?
是 Java 泛型中的通配符,用于表示未知类型。它可以在泛型类或方法中使用,以实现更灵活的类型匹配。?
通配符常与 extends
和 super
关键字结合使用,形成类型边界。
6.1 无界通配符 ?
?
可以单独使用,表示任意类型,但不能对其进行任何假设和操作。
import java.util.ArrayList;
import java.util.List;
public class ClassReflectDemo {
public static void main(String[] args) {
// 创建不同类型的 ArrayList
List<String> stringList = new ArrayList<>();
stringList.add("Hello");
stringList.add("World");
List<Integer> intList = new ArrayList<>();
intList.add(1);
intList.add(2);
List<Double> doubleList = new ArrayList<>();
doubleList.add(3.14);
doubleList.add(2.718);
// 使用无界通配符的泛型方法来打印列表内容
printList(stringList); // 输出: [Hello, World]
printList(intList); // 输出: [1, 2]
printList(doubleList); // 输出: [3.14, 2.718]
}
// 定义一个可以接受任意类型列表的泛型方法
public static void printList(List<?> list) {
System.out.println(list);
}
}
运行结果:
[Hello, World]
[1, 2]
[3.14, 2.718]
6.2 上界通配符 ? extends T
? extends T
表示通配符类型可以是 T
或 T
的子类,用于读取但不写入。
import java.util.ArrayList;
import java.util.List;
public class ClassReflectDemo {
public static void main(String[] args) {
// 创建不同类型的列表
List<Integer> integerList = new ArrayList<>();
integerList.add(10);
integerList.add(20);
List<Double> doubleList = new ArrayList<>();
doubleList.add(3.14);
doubleList.add(2.718);
// 调用使用上界通配符的方法
printList(integerList); // 输出: [10, 20]
printList(doubleList); // 输出: [3.14, 2.718]
}
// 定义一个可以接受 Number 及其子类的泛型方法
public static void printList(List<? extends Number> list) {
System.out.println(list);
}
}
运行结果:
[10, 20]
[3.14, 2.718]
6.3 下界通配符 ? super T
? super T
表示通配符类型可以是 T
或 T
的父类,用于写入但不读取。
import java.util.ArrayList;
import java.util.List;
public class ClassReflectDemo {
public static void main(String[] args) {
// 创建一个可以接受 Integer 类型及其父类的 List
List<Number> numberList = new ArrayList<>();
numberList.add(1); // 添加 Integer 类型的对象
numberList.add(2.5); // 添加 Double 类型的对象
// 使用下界通配符的方法
printList(numberList);
// 打印结果
for (Number number : numberList) {
System.out.println(number);
}
}
// 使用下界通配符来接受 Integer 类型及其父类的集合
public static void printList(List<? super Integer> list) {
list.add(10); // 可以安全地添加 Integer 类型的对象
list.add(20);
// 注意:由于使用了下界通配符,读取 list 中的元素时只能保证类型是 Object 或其子类型
// 因此不能执行例如:Integer num = list.get(0); 这样具体类型的操作
// list.get(0) 返回的类型被视为 Object,
// 因为 list 可能是 List<Number> 或 List<Object>。
// 编译器无法确定 list.get(0) 实际上是 Integer,因此直接将返回值赋给 Integer 变量是不安全的。
}
}
运行结果:
1
2.5
10
20
小结:
上界通配符 ? extends T
:允许你读取 T
或 T
的子类型的对象,但通常不允许写入,因为你无法确定具体的子类型。
下界通配符 ? super T
:允许你写入 T
或 T
的子类型对象,因为容器至少是 T
类型的父类,但读取时可能只能获取 Object
类型。