反射
# 反射
# 概念理解
Java 的反射机制是指在运行状态中,对于任意一个类都能够知道这个类所有的属性和方法;并且对于任意一个对象,都能够调用它的任意一个方法;这种动态获取信息以及动态调用对象方法的功能成为 Java 语言的反射机制。
代码 + 白话解释:
我们在编写代码时,当需要使用到某一个类的时候,都会先了解这个类是做什么的。然后实例化这个类,接着用实例化好的对象进行操作,就像这样:
Student student = new Student();
student.study("语文");
1
2
2
反射就是,一开始并不知道我们要初始化的类对象是什么,自然也无法使用 new
关键字来创建对象了。
Class clazz = Class.forName("reflection.Student");
Method method = clazz.getMethod("study", String.class);
Constructor constructor = clazz.getConstructor();
Object object = constructor.newInstance();
method.invoke(object, "语文");
1
2
3
4
5
2
3
4
5
以上两段代码,执行效果是一样的,差别主要是在实现过程上:
- 第一段代码在未运行前就已经知道了要运行的类是
Student
。 - 第二段代码则是到整个程序运行的时候,从字符串
reflection.Student
,才知道要操作的类是Student
。
所以,反射就是在运行时才知道要操作的类是什么,并且可以在运行时获取类的完整构造,并调用对应的方法。
# Class 类对象
# 是啥
在 Java 中,Class 类对象是一个特别的对象,它表示 Java 运行时中某个类的类型信息,这里的类型信息指的是关于该类的所有数据:类名、类的修饰符、父类、实现的接口、构造函数、方法、字段等等等等。
每当我们定义一个类时,Java 虚拟机(JVM)会自动为这个类生成一个 Class 类对象,用于表示该类的元数据。每个类在内存中只有一个 Class 类对象。
# 过程
类对象的创建过程如下:
- 编译时:在编译阶段,Java 编译器(如 javac)会将源代码(.java 文件)编译成字节码(.class 文件)。这些字节码文件包含了类的结构和方法的详细信息,但此时还没有创建 Class 类对象。
- 类加载时:在运行时,当 JVM 首次使用某个类时,会加载该类的字节码并创建对应的 Class 类对象。这一步骤称为类加载(Class Loading)。
- Class 类对象创建:在类加载过程中,JVM 会为每个加载的类生成一个 Class 类对象,用于表示该类的类型信息。此 Class 类对象会包含类的元数据(如类名、字段、方法等)。
例如,当我们在运行代码时首次引用某个类:
public class Main {
public static void main(String[] args) {
MyClass myClass = new MyClass(); // 类加载,Class 类对象创建
}
}
1
2
3
4
5
2
3
4
5
当代码执行到 MyClass myClass = new MyClass();
时,JVM 会加载 MyClass 类,并创建相应的 Class 类对象。
# 用途
- 反射:通过 Class 类对象,我们可以使用 Java 反射机制获取类的构造函数、方法、字段等信息,并可以动态地创建实例或调用方法。
- 类型检查:在运行时可以使用 Class 类对象进行类型检查。
- 框架:许多 Java 框架和库(如 Spring、Hibernate)依赖于 Class 类对象来实现动态代理、依赖注入等功能。
# 基本使用
# 获取 Class 类对象
获取 Class 对象有三种方法。
- 第一种,
使用 Class.forName
静态方法。
Class class1 = Class.forName("reflection.TestReflection");
1
- 第二种,使用类的
.class
方法。
Class class2 = TestReflection.class;
1
- 第三种,使用实例对象的
getClass()
方法。
TestReflection testReflection = new TestReflection();
Class class3 = testReflection.getClass();
1
2
2
# 反射的基本 API
- 实例化对象
// 方式一
Class class1 = Class.forName("reflection.Student");
Student student = (Student) class1.newInstance();
System.out.println(student);
// 方式二
Constructor constructor = class1.getConstructor();
Student student1 = (Student) constructor.newInstance();
System.out.println(student1);
1
2
3
4
5
6
7
8
9
2
3
4
5
6
7
8
9
- 获取类的构造函数(可能有多个)
Class class1 = Class.forName("reflection.Student");
Constructor[] constructors = class1.getDeclaredConstructors();
for (int i = 0; i < constructors.length; i++) {
System.out.println(constructors[i]);
}
1
2
3
4
5
2
3
4
5
- 获取类的成员变量
getField(String name)
方法只能访问公有字段,否则会抛出NoSuchFieldException
异常。getDeclaredField(String name)
方法可以获取私有字段,但仍然需要设置其可访问性。
public class Student {
// 私有属性 age
private Integer age;
// 公有属性 email
public String email;
}
public class TestReflection {
public static void main(String[] args) throws ClassNotFoundException, NoSuchFieldException {
// 动态加载 Student 类
Class class1 = Class.forName("reflection.Student");
// 获取公有字段 email
Field email = class1.getField("email");
System.out.println(email);
// 获取私有字段 age,并设置其可访问性
Field age = class1.getDeclaredField("age");
age.setAccessible(true);
System.out.println(age);
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
- 获取类的方法
public class Student {
private void testPrivateMethod() {}
public void testPublicMethod() {}
}
public class TestReflection {
public static void main(String[] args) throws ClassNotFoundException, NoSuchFieldException {
Class class1 = Class.forName("reflection.Student");
Method[] methods = class1.getMethods();
for (int i = 0; i < methods.length; i++) {
System.out.println(methods[i]);
}
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 反射的应用
- 很多框架都使用了反射技术,如 Spring,Mybatis,Hibernate 等。
- Spring 通过 XML 配置模式装载 Bean,就是反射的一个典型例子:
- 加载 XML 配置文件 -> 解析 XML 里内容,得到相关字节码信息 -> 使用反射机制,得到 Class 实例 -> 动态配置实例的属性并使用
- 好处:不用每次都去
new
实例了,并且可以修改配置文件,比较灵活。
- JDBC 的数据库的连接
- 在 JDBC 连接数据库中,一般包括加载驱动,获得数据库连接等步骤。而加载驱动,就是引入相关 Jar 包后,通过
Class.forName()
即反射技术,加载数据库的驱动程序。
- 在 JDBC 连接数据库中,一般包括加载驱动,获得数据库连接等步骤。而加载驱动,就是引入相关 Jar 包后,通过
# 反射的问题
- 使用反射有性能开销,尤其在类加载的过程中会比较慢(java-reflection-why-is-it-so-slow (opens new window))
- 安全问题:反射可以获取类中的方法和构造器,修改访问权限,某种程度上来说这不一定是安全的(比如单例模式中,会将构造器设计为私有,以此防止从外部构造对象)
(完)