Java反射原理
Java反射原理
反射原理
反射是 Java 语言中的一个重要特性,它允许程序在运行时动态地获取类的信息并操作类的对象、方法、属性等,其原理主要基于 Java 虚拟机(JVM)对类的加载、存储和访问机制,以下是详细解释:
反射的原理
1. 类的加载与运行时表示
-
在 Java 中,当程序启动时,JVM 会负责加载需要的类文件(
.class
文件)到内存中。类加载过程包括加载、验证、准备、解析和初始化等阶段。一旦类被加载到内存,JVM 会为其创建一个对应的Class
对象来表示该类在运行时的状态。这个Class
对象就像是类的一个 “镜像”,它包含了类的所有元数据信息,如类名、父类信息、接口信息、字段信息、方法信息、构造函数信息等。
2. 通过Class
对象获取类信息
-
反射机制就是通过获取这个
Class
对象来进一步挖掘类的各种信息。例如,可以通过Class
对象的getDeclaredFields()
方法获取类的所有声明字段(包括私有字段),通过getDeclaredMethods()
方法获取类的所有声明方法(包括私有方法),通过getConstructors()
方法获取类的所有公共构造函数等。这些方法返回的分别是Field
、Method
、Constructor
等类型的数组,每个元素对应着类中的一个具体字段、方法或构造函数。
3. 动态操作类的实例、方法和属性
-
在获取到类的相关信息后,反射就可以进一步实现对类的动态操作。比如,要创建一个类的实例,可以使用
Class
对象的newInstance()
方法(在 Java 9 及以后版本,更推荐使用getDeclaredConstructor()
结合newInstance()
来创建实例,以处理更复杂的构造函数情况)。对于获取到的Method
对象,可以通过调用其invoke()
方法来动态地执行该方法,传入相应的参数就可以实现像在正常代码中调用方法一样的效果,只是这里是在运行时动态进行的。同样,对于Field
对象,可以通过get()
和set()
方法来获取和设置类实例中的字段值,即使是私有字段也可以通过设置Accessible
属性为true
来突破访问限制进行操作。
和new创建对象的区别
虽然通过反射和使用 new
关键字创建对象最终都能获取到代表类的 Class
对象,但它们在很多方面存在明显区别,以下是详细介绍:
1. 创建对象的时机和灵活性
-
使用
new
关键字:-
创建对象的时机是在代码编译阶段就基本确定了。当你在代码中编写
new
关键字来创建某个类的实例时,编译器在编译过程中就知道要创建这个类的对象,并且会按照类的定义和构造函数的要求来生成相应的字节码指令,以便在程序运行时直接执行这些指令来创建对象。例如:MyClass myObj = new MyClass();
,在编译时就确定了要创建MyClass
类的对象。 -
灵活性相对较低。因为是在编译时就确定了要创建的对象类型,所以如果在运行时需要根据不同的条件创建不同类型的对象,使用
new
就会比较麻烦。比如,你想根据用户的输入来决定创建A
类还是B
类的对象,单纯依靠new
关键字就难以实现,需要编写大量的条件判断代码来分别处理不同情况。
-
-
使用反射机制:
-
创建对象的时机是在运行时动态确定的。反射可以根据程序运行过程中的各种条件(如用户输入、配置文件信息、运行时环境变化等)来决定是否创建对象以及创建哪种类型的对象。例如,通过
Class.forName("com.example.MyClass").newInstance();
(这里只是简单示例,实际应用可能更复杂),可以在运行时根据字符串形式的类名(这里是"com.example.MyClass"
)来动态创建该类的对象,这种方式允许在运行时灵活地根据不同情况做出决策。 -
具有很高的灵活性。反射能够在运行时根据不同的需求动态地创建不同类型的对象,无需在编译时就确定具体要创建的对象类型。这对于一些需要根据运行时情况进行动态调整的应用场景,如插件式架构、动态加载类库等,非常有用。
-
2. 访问权限控制
-
使用
new
关键字:-
当使用
new
关键字创建对象时,只能访问类的公共构造函数和公共成员(属性和方法)。如果类的构造函数或成员是私有的、受保护的或默认访问权限(即没有显式声明访问权限,在同一个包内可访问),在使用new
创建对象后,从外部是无法直接访问这些具有受限访问权限的部分的。例如,对于一个有私有构造函数的类,如:
-
class PrivateConstructorClass { private PrivateConstructorClass() { } }
试图用 new
关键字创建这个类的对象时,会因为无法访问私有构造函数而导致编译错误。
-
使用反射机制:
-
反射在一定程度上可以突破访问权限的限制。通过反射,即使类的构造函数、属性或方法是私有的,也可以通过设置相应的可访问标志(如
Field
、Method
、Constructor
对象的setAccessible(true)
方法)来访问这些受限部分。例如,对于上面那个有私有构造函数的类,可以通过反射这样来创建对象:
-
import java.lang.reflect.Constructor; class Main { public static void main(String[] args) throws Exception { Class<?> clazz = PrivateConstructorClass.class; Constructor<?> constructor = clazz.getDeclaredConstructor(); constructor.setAccessible(true); PrivateConstructorClass obj = (PrivateConstructorClass) constructor.newInstance(); } }
在上述代码中,通过反射获取到私有构造函数,并设置其可访问标志为 true
,然后就可以成功创建该类的对象。
3. 对类的了解程度要求
-
使用
new
关键字:-
在使用
new
关键字创建对象时,编译器需要在编译时就对要创建对象的类有完整的了解。这意味着在编译代码时,必须已经存在该类的定义(要么在同一个项目中已经导入了该类,要么在类路径上可以找到该类的字节码文件),并且编译器能够根据类的定义准确地生成创建对象的字节码指令。例如,如果要创建一个MyClass
类的对象,在编译时就需要知道MyClass
的类名、构造函数的形式、成员的类型等信息。
-
-
使用反射机制:
-
反射可以在对类的了解相对较少的情况下进行操作。只需要知道类的名称(以字符串形式)或者能够获取到代表类的
Class
对象,就可以通过反射来对类进行一些基本的操作,如获取类的部分信息(如字段、方法等)、创建对象等。例如,通过Class.forName("com.example.MyClass")
,只需要知道类的全限定类名,就可以尝试获取该类的Class
对象,进而进行后续的反射操作,而不需要在编译时就对该类的所有细节有深入的了解。
-
4. 性能方面
-
使用
new
关键字:-
通常情况下,使用
new
关键字创建对象的性能相对较好。因为在编译时就确定了创建对象的方式和流程,在运行时只需要按照预定义的字节码指令执行即可,不需要进行额外的查找、分析等操作。例如,在频繁创建同一类型对象的场景下,如循环中创建多个MyClass
类的对象,使用new
关键字创建的速度会比较快。
-
-
使用反射机制:
-
反射的性能相对较差。由于反射是在运行时动态地获取类信息、创建对象、执行方法等操作,这涉及到较多的查找、分析和动态调用等过程,会消耗更多的资源和时间。例如,同样是创建多个
MyClass
类的对象,如果使用反射机制(如通过Class.forName("com.example.MyClass").newInstance();
),相比使用new
关键字,会花费更多的时间和可能占用更多的资源,尤其是在对性能要求较高的场景下,反射的性能劣势会更加明显。
-
综上所述,反射和使用 new
关键字创建对象虽然都能与类的 Class
对象产生关联,但它们在创建对象的时机、灵活性、访问权限控制、对类的了解程度要求以及性能等方面存在显著差异,在实际应用中应根据具体的需求和场景选择合适的方式来创建对象。
原文地址:https://blog.csdn.net/qq_62097431/article/details/143415392
免责声明:本站文章内容转载自网络资源,如本站内容侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!