“JAVA反射机制详解”
什么是反射?
反射是与正射相对应的,正射就是在使用某个类之前就已经知道了这个类的类型等等,可以直接使用new关键字调用构造方法进行初始化,之后就可以正常使用这个类的的对象了
Java 反射机制是在运行状态中,对于任意一个类,都能够知道这个类中的所有属性和方法,对于任意一个对象,都能够调用它的任意一个方法和属性;这种动态获取的信息以及动态调用对象的方法的功能称为Java 语言的反射机制。
反射的特点
- 运行时类信息访问:反射允许程序在运行时获取类的完结构信息,包括类名、包名、父类、实现的接口、构造函数、方法和字段等等
- 动态对象创建:可以使用反射API动态地创建对象实例,即使在编译时不知道具体的类名。这是通过Class类的newInstance()方法或Constructor对象的newnstance()方法实现的。
- 动态方法调用:可以在运行时动态地调用对象的方法,包括私有方法。这通过Method类的invoke()方法
实现,允许你传入对象实例和参数值来执行方法。
4.访问和修改字段值:反射还允许程序在运行时访问和修改对象的字段值,即使是私有的。这是通过Field类的qet()和set()方法完成的
反射的缺点
破坏封装:由于反射允许访问私有字段和私有方法,所以可能会破坏封装而导致安全问题
性能开销:由于反射涉及到动态解析,因此无法执行Java 虚拟机优化,再加上反射的写法的确要复杂得多,所以性能要比“正射”差很多,在一些性能敏感的程序中应该避免使用反射。
反射的应用场景
- 通用框架中加载配置文件:类似spring、springboot等,为了保持通用性,通过不同的配置文件来加载不同的对象,比如数据库驱动等等
- 动态代理:在面向切面编程中,需要拦截特定的方法,就会选择动态代理的方式,动态代理的底层实现就是利用了反射
- 注解:注解本身不会直接影响程序的执行,但可以通过反射机制在运行时获取和处理这些注解
反射的基本使用
在 Java 中,Class 对象是一种特殊的对象,它代表了程序中的类和接口。Java 中的每个类型(包括类、接口、数组以及基础类型)在 JVM 中都有一个唯一的 Class 对象与之对应。这个 Class 对象被创建的时机是在 JVM 加载类时,由 JVM 自动完成。Class 对象中包含了与类相关的很多信息,如类的名称、类的父类、类实现的接口、类的构造方法、类的方法、类的字段等等。这些信息通常被称为元数据(metadata)。
获取类的Class对象
共有三种方法:
- 通过类的全名获取Class对象
1
Class clazz = Class.forName("com.xxx");
- 通过 Class 对象获取构造方法 Constructor 对象
1
Constructor constructor = clazz.getConstructor();
- 通过 Constructor 对象初始化反射类对象
1
Object object = constructor.newInstance();
- 获取要调用的方法的 Method 对象
1
2Method setNameMethod = clazz.getMethod("setName", String.class);
Method getNameMethod = clazz.getMethod("getName"); - 通过 invoke() 方法执行
1
2setNameMethod.invoke(object, "zhangsan");
getNameMethod.invoke(object)
反射底层机制
使用反射,必须要获得反射类的Class对象,每一个类只有一个Class对象,无论这个类生成了多少个对象。这个Class对象是由JAVA虚拟机生成的,由它来获取这个类的所有结构信息
也就是说,java.lang.Class 是所有反射 API 的入口
但是方法的反射调用都是通过Method对象的invoke()方法完成的
1 | public Object invoke(Object obj, Object... args) |
invoke() 方法实际上是委派给 MethodAccessor 接口来完成的
MethodAccessor 接口有三个实现类,其中的 MethodAccessorImpl 是一个抽象类,另外两个具体的实现类 NativeMethodAccessorImpl 和 DelegatingMethodAccessorImpl 继承了这个抽象类。
NativeMethodAccessorImpl:通过本地方法来实现反射调用;
DelegatingMethodAccessorImpl:通过委派模式来实现反射调用;
invoke() 方法在执行的时候,会先调用 DelegatingMethodAccessorImpl,然后调用 NativeMethodAccessorImpl,最后再调用实际的方法
为什么不直接调用本地实现呢?
之所以采用委派实现,是为了能够在本地实现和动态实现之间切换。动态实现是另外一种反射调用机制,它是通过生成字节码的形式来实现的。如果反射调用的次数比较多,动态实现的效率就会更高,因为本地实现需要经过 Java 到 C/C++ 再到 Java 之间的切换过程,而动态实现不需要;但如果反射调用的次数比较少,反而本地实现更快一些。而这个临界点默认是15次,可以通过 -Dsun.reflect.inflationThreshold 参数类调整
反射常用的API
Class.forName(),参数为反射类的完全限定名。
类名 + .class,只适合在编译前就知道操作的 Class。
获取构造器、字段、方法等
getConstructor():返回反射类的特定 public 构造方法,可以传递参数,参数为构造方法参数对应 Class 对象;缺省的时候返回默认构造方法。
getDeclaredConstructor():返回反射类的特定构造方法,不限定于 public 的。
getConstructors():返回类的所有 public 构造方法。
getDeclaredConstructors():返回类的所有构造方法,不限定于 public 的。
获取字段和方法都是类似,换成Field或者Method就行
注意,如果反射访问私有字段和(构造)方法的话,需要使用 Constructor/Field/Method.setAccessible(true) 来绕开 Java 语言的访问限制
注:参考javabetter和xiaolincoding,侵删
install_url
to use ShareThis. Please set it in _config.yml
.