Ⅰ. 概念与意义
Java 反射是一种机制,允许程序在运行时 :
-
查看类的信息(如属性、方法、构造器等)
-
动态创建对象
-
访问和修改属性
-
调用方法
-
操作泛型参数、注解等元信息
反射的意义和应用场景如下所示:
| 应用场景 | 描述 | 举例 |
|---|---|---|
| 框架设计核心 | Spring、Hibernate、MyBatis 等全靠它 | 动态注入Bean、解析注解、AOP |
| 通用工具类/库开发 | 不知道具体类时也能处理 | JSON序列化、ORM字段映射 |
| 插件机制 / 动态加载类 | 运行时加载未编译进来的类 | SPI机制、Class.forName() |
| 动态代理 & AOP 编程 | 实现日志增强、权限控制等 | JDK动态代理、AspectJ |
| 测试 & Mock 框架 | 访问私有字段,模拟行为 | JUnit, Mockito 等 |
| 简化开发 / 规避重复代码 | 构建泛型代码库 | 动态调用 setter/getter |
使用反射的风险和代价如下所示:
| 风险/代价 | 描述 |
|---|---|
| ⏱ 性能开销 | 反射是解释型操作,慢于直接调用 |
| 🔒 安全问题 | 可访问私有字段/方法,绕过封装 |
| 🧩 可读性差 | 代码不直观,难调试 |
| 🧱 编译器检查失效 | 编译时不知道是否写错方法名或类型 |
Ⅱ. 反射相关的类与操作
| 类名 | 用途 |
|---|---|
| Class类 | 代表类的实体,在运行的Java应用程序中表示类和接口 |
| Field类 | 代表类的成员变量、属性 |
| Method类 | 代表类的方法 |
| Constructor类 | 代表类的构造方法 |
| Annotation类 | 代表类的注解 |
一、Class类(一切反射的起点)
Java 文件被编译后,生成了 .class 文件,JVM 此时会去解读这个 .class 文件,而 .class 文件也被 JVM 解析为一个对象,这个对象就是 java.lang.Class。
通过 Java 的反射机制应用到这个实例,就可以去获得甚至去添加改变这个类的属性和动作,使得这个类成为一个动态的类。
一旦拿到了 Class 对象,比如:
Class<?> clazz = Person.class; // 具体获取方式看后面可以做很多事,比如下面的操作:(剩下的查文档)
| 方法 | 用途 |
|---|---|
clazz.``getName``() |
获取类名 |
clazz.``getDeclaredFields``() |
获取所有字段(所有权限均可访问) |
clazz.``getDeclaredField``(String name) |
获取指定字段(所有权限均可访问)(下面方法也是类似) |
clazz.``getDeclaredMethods``() |
获取所有方法(所有权限均可访问) |
clazz.``getDeclaredConstructors``() |
获取构造器(所有权限均可访问) |
clazz.newInstance() |
创建对象(已废弃,推荐 getConstructor().newInstance()) |
clazz.getSuperclass() |
获取父类 |
clazz.getInterfaces() |
获取实现的接口 |
clazz.isInterface() |
是否是接口 |
clazz.isAnnotationPresent(XXX.class) |
是否被某注解标记 |
clazz.getLoader() |
获得类的加载器 |
二、获取Class类的三种方式(Student类见附录)
| 方式 | 语法 | 说明 |
|---|---|---|
Class.forName("全限定名") |
Class.forName("com.demo.Person") |
常用于配置、动态加载 |
类静态变量 class |
Person.class |
编译期就能获得类信息 |
对象.getClass() |
obj.getClass() |
运行时获得对象的真实类型 |
public static void main(String[] args){
*// 1. 使用Class.forName(),注意要指定包名,并且最好捕捉异常*
* *Class<?> c1 = null;
try {
c1 = Class.*forName*("Reflection.Student");
} catch(ClassNotFoundException e) {
e.printStackTrace();
}
*// 2. 使用指定类的class静态变量*
* *Class<?> c2 = Student.class;
*// 3. 使用对象实例的getClass()方法*
* *Student st = new Student();
Class<?> c3 = st.getClass();
*// 一个类在JVM中只会有一个Class实例,因此c1、c2、c3都指向同一个Class实例*
* *System.*out*.println(c1 == c2); *// true*
* *System.*out*.println(c2 == c3); *// true*
* *System.*out*.println(c3 == c1); // true
}☠注意事项:
-
一个类在
JVM中只会有一个Class实例 。不管你创建多少对象,它们的.getClass()是同一个。 -
泛型信息在运行时会被擦除,可以参考泛型笔记。
-
Class.forName()会触发类初始化,会执行静态代码块,慎用 !
三、创建对象的两种方式
① 直接调用 Class 中的 newInstance() 方法
第一种方式就是调用 Class 对象中的 newInstance() 方法,这种方法现在已经被淘汰了,原因如下所示:
-
只能调用 无参构造方法
-
如果构造方法不是
public,会抛异常 -
已被标记为 过时方法 (从
JDK 9起)
public static void main(String[] args) {
try {
*// 1. 先获取Class对象*
* *Class c = Class.*forName*("Reflection.Student");
*// 2. 然后调用newInstance()方法创建对象,注意要强转成Student类型*
* *Student p = (Student)c.newInstance();
System.*out*.println(p);
} catch (ClassNotFoundException e) {
throw new RuntimeException(e);
} catch (InstantiationException e) {
throw new RuntimeException(e);
} catch (IllegalAccessException e) {
throw new RuntimeException(e);
}
}② 生成 Constructor 构造方法对象
第二种方式先调用 Class 对象中的 getConstructor() 系列的方法,得到 Constructor 对象,然后调用该对象中的 newInstance() 方法,优势如下所示:
-
可调用有参构造方法
-
可修改构造器可见性(
setAccessible(true))
public static void main(String[] args) {
try {
*// 1. 获取Class对象*
* *Class<?> cs = Class.*forName*("Reflection.Student");
*// 2. 获取无参的构造方法来创建对象*
* *Constructor<?> non_arg_constructor = cs.getDeclaredConstructor();
Student s1 = (Student)non_arg_constructor.newInstance();
System.*out*.println(s1);
*// 3. 获取带参的构造方法来创建对象,因为带参构造方法是私有的,所以要设置权限为true*
* *Constructor<?> arg_constructor = cs.getDeclaredConstructor(String.class, int.class);
arg_constructor.setAccessible(true);
Student s2 = (Student)arg_constructor.newInstance("liren", 18);
System.*out*.println(s2);
} catch (ClassNotFoundException e) {
throw new RuntimeException(e);
} catch (NoSuchMethodException e) {
throw new RuntimeException(e);
} catch (InvocationTargetException e) {
throw new RuntimeException(e);
} catch (InstantiationException e) {
throw new RuntimeException(e);
} catch (IllegalAccessException e) {
throw new RuntimeException(e);
}
}
// 运行结果:
Student()
Student{name='bit', age=18}
Student(String,name)
Student{name='liren', age=18}四、操作类的字段(例子)
public static void main(String[] args) {
*// 使用反射操作字段*
* *try {
Class<?> cs = Class.*forName*("Reflection.Student");
*// 1. 输出类名,打印字段信息*
* *System.*out*.println("ClassName: " + cs.getName());
for(Field e : cs.getDeclaredFields()) {
System.*out*.println(e.getName() + " " + e.getType().getName() + " " + Modifier.*toString*(e.getModifiers()));
}
*// 2.1 获取私有属性name*
* *Field namefield = cs.getDeclaredField("name");
namefield.setAccessible(true); *// 对于私有属性,需要设置Accessible为true*
* // 2.2 获取该类对象,并设置name属性的值,然后用namefield.get()获取这个私有值*
* *Student st = (Student)cs.newInstance();
namefield.set(st, "liren");
System.*out*.println("反射私有属性修改了name:" + namefield.get(st));
} catch (ClassNotFoundException | NoSuchFieldException e) {
throw new RuntimeException(e);
} catch (InstantiationException e) {
throw new RuntimeException(e);
} catch (IllegalAccessException e) {
throw new RuntimeException(e);
}
}
// 运行结果:
ClassName: Reflection.Student
name java.lang.String private
age int public
Student()
反射私有属性修改了name:liren五、操作类的方法(例子)
public static void main(String[] args) {
try {
Class<Student> cs = Student.class;
*// 获取带参的私有方法,然后设置权限,最后使用invoke()方法进行调用*
* *Method func_method = cs.getDeclaredMethod("function", String.class);
func_method.setAccessible(true);
func_method.invoke((Student)cs.newInstance(), "我是给私有的function函数传的参数");
*// 还可以输出方法的指定信息*
* *System.*out*.println("方法名:" + func_method.getName());
System.*out*.println("修饰符为:" + Modifier.*toString*(func_method.getModifiers()));
} catch (NoSuchMethodException e) {
throw new RuntimeException(e);
} catch (InvocationTargetException e) {
throw new RuntimeException(e);
} catch (IllegalAccessException e) {
throw new RuntimeException(e);
} catch (InstantiationException e) {
throw new RuntimeException(e);
}
}
// 运行结果:
Student()
我是给私有的function函数传的参数
方法名:function
修饰符为:private附录
class Student {
*// 私有属性name*
* *private String name = "bit";
*// 公有属性age*
* *public int age = 18;
*// 不带参数的构造⽅法*
* *public Student() {
System.*out*.println("Student()");
}
private Student(String name, int age) {
this.name = name;
this.age = age;
System.*out*.println("Student(String,name)");
}
private void eat() {
System.*out*.println("i am eat");
}
void sleep() {
System.*out*.println("i am pig");
}
private void function(String str) {
System.*out*.println(str);
}
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}