前言
本人近期在研究spring源码和caffeine源码的时候,发现其底层运用了java反射机制,以及在jdk7后新增的java.lang.invoke包下面的类,发现对其掌握程度不足,故专门对其整理总结。
了解java反射机制
java的反射API能够使java程序在运行当中获取类的域(字段)、方法构造器等,并且可以与这些元素交互,例如使用获取到的构造器实例化一个对象,为字段赋值,执行获取到的方法等。这为java语言提供了动态性。Java反射机制被称为框架设计的灵魂。而若想执行上述操作,需要借助Class实例对象。
什么是Class对象?
按照官方文档的描述,在运行时的java程序中,Class实例对象代表着类(classes)和接口(interfaces)。因为枚举被当成类(class)而注解是接口(interface)类型,所以枚举型和注解都会有对应的Class实例对象(在运行中)。而对于数组数组类型,拥有相同元素类型和维数(一维数组、二维数组、三维数组…)的数组,它们都属于一个由相同的Class对象所反映的一个类。而对于java的基本类型(boolean,
byte,
char,
short,
int,
long,
float`, double),甚至是关键字void,都会各被一个Class实例对象所表示。**可以简单的理解为Class实例对象(类对象)它封装了一个类的基本信息,我们可以从这些信息里面得知它封装的类有哪些构造器、类名是什么、有哪些方法字段、父类的Class、所实现接口的Class等。**其中String.class所返回的实例对象的类型是Class,若被Class建模的类(可以简单理解成封装)如果是不确定的,那么用Class<?>来表示。
Class实例对象是怎么来的?
Class它没有公开的构造器,相反它是在类加载过程时由java虚拟机自动构造,并且通过调用类加载器的defineClass方法来构造的,所以说它是在类加载的时候被创建的。而所谓的类加载就是把 .class 文件加载到内存当中。
通过Class实现反射
了解了基本的信息后就可以通过Class实例来实现反射机制了,首先来看Class实例封装了它所代表的类的哪些常用信息。
类型 | 描述 |
---|---|
Constructor | 类相应的构造函数,有一个或多个 |
Annotation | 标注在该类上的注解 |
Field | 该类拥有的字段(域),只能反射得到用public修饰的字段 |
Method | 该类拥有的方法,可以反射pulibc、protect、(default)、private修饰的方法 |
AnnotatedType | 用于表示Class实例所表示的类的父类或者接口 |
当然常用封装信息还有类名(全限定和简写的都有)、包名等。
反射构造器
以下几种为获取构造器的方法
返回类型 | 方法名 |
---|---|
Constructor | getConstructor(Class<?>…parameterTypes) |
Constructor<?>[] | getConstructors() |
Constructor | getDeclaredConstructor(Class<?>…paramenterType) |
Constructor<?>[] | getDeclaredConstructors() |
反射API的命名其实是很浅显易懂的,例如第一个方法就是最常用的根据构造器的参数来指定返回某个构造器,而第二个方则是返回所有构造器。而下面两个方法名都在原来的基础上都加了个"Declared",它的意思是"被声明",所以下面两个方法返回的构造器都是在Class实例所代表的类中声明过的,而不包括继承得来的。
以下通过得到的构造器来实例化对象
返回类型 | 方法名 |
---|---|
T | newInstance(Object…initargs) |
通过构造器实例化对象只有一个方法,只要传入相应的参数即可很简单,下面演示一个构造方法为变长字符串的特殊情况。
public class ReflectTest {
public ReflectTest(String...name) {
System.out.println(Arrays.toString(name));
}
public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException, NoSuchFieldException {
//因为变长字符串本质就是一个数组,所以通过String[].class作为参数来获取构造器
Constructor<ReflectTest> declaredConstructor = ReflectTest.class.getDeclaredConstructor(String[].class);
//转为Object对象避免歧义
declaredConstructor.newInstance((Object) new String[]{"小明","xiaohogn"});
}
}
当然获取到的构造器当然不是只有用来实例化对象那么简单,我通过官方文档总结了以下几种常用的,如果想了解更多请阅读jdk8的api文档。
返回类型 | 方法名 |
---|---|
Annotation[] | getDeclaredAnnotations() |
此方法用于返回直接存在于构造器上的注解
public class ReflectTest {
@MyAnnotation
public ReflectTest(String...name) {
System.out.println(Arrays.toString(name));
}
public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException, NoSuchFieldException {
Constructor<ReflectTest> declaredConstructor = ReflectTest.class.getDeclaredConstructor(String[].class);
System.out.println(declaredConstructor.getAnnotatedReceiverType());
}
}
//得到结果
[@com.test.reflectTest.MyAnnotation()]
这方法对于框架底层实现非常重要,因为很多框架都会自定义一些注解,而我们可以通过包扫描的时候判断类上、构造器上、方法上有存在什么注解,然后执行对应的操作。
反射方法
以下几种为获取类方法的方法
返回类型 | 方法名 |
---|---|
Method | getMethod(String name,Class<?>…parameterTypes) |
Method[] | getMethods() |
Method | getDeclaredMethod(String name,Class<?>…parameterTypes) |
Method[] | getDeclaredMethods() |
与获取构造方法类似,上面两种获取全部的方法(包括继承的),而下面两种只获取在类中定义存在的。
Mehod中执行方法的方法
返回类型 | 方法名 |
---|---|
Object | invoke(Object obj,Object…args) |
其中要注意的是,要通过Method执行静态方法和非静态方法是不同的,并且执行私有方法时要设置setAccessible(true),因为下面测试是写在同一类中的main方法中,其实加不加也可以。
public class ReflectTest {
public void publicMethod(String str){
System.out.println("执行公用方法"+str);
}
private void privateMethod(String str){
System.out.println("执行私有方法"+str);
}
public static void staticMethod(String str){
System.out.println("执行静态方法"+str);
}
public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException, NoSuchFieldException {
//通过反射,共有方法和私有方法都可以执行
ReflectTest reflectTest = new ReflectTest();
Method publicMethod = ReflectTest.class.getDeclaredMethod("publicMethod", String.class);
System.out.println("接收类型:"+publicMethod.getAnnotatedReceiverType());
publicMethod.invoke(reflectTest,"共有方法");
Method privateMethod = ReflectTest.class.getDeclaredMethod("privateMethod", String.class);
//调用私有前要设置
privateMethod.setAccessible(true);
privateMethod.invoke(reflectTest,"私有方法");
//执行静态方法
//执行静态方法时,不需要向第一个参数传入实例对象,传一个null即可
Method staticMethod = ReflectTest.class.getDeclaredMethod("staticMethod", String.class);
staticMethod.invoke(null,"执行啦");
}
}
//结果
执行公用方法共有方法
执行私有方法私有方法
执行静态方法执行啦
反射字段(域)
通过Class实例获取字段有以下几种方法
返回类型 | 方法名 |
---|---|
Field | getDeclaredField(String name) |
Field[] | getDeclaredFields() |
Field | getField(String name) |
Fild[] | getFields() |
和之前的方法类似,这里就不罗嗦了。值得注意的是,通过Class只能反射由public修饰的字段。
对于获取到的Field,最常用的就是get、set方法了
返回类型 | 方法名 |
---|---|
Object | get(Object obj) |
void | set(Object obj,Object value) |
public class ReflectTest {
static String yourName;
public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException, NoSuchFieldException {
//静态域不需要传实例的引用
Field yourName = ReflectTest.class.getDeclaredField("yourName");
yourName.set(null,"小涛");
System.out.println(yourName.get(null));
}
}
//结果
小涛
反射注解
获取标注在类上的注解也有两个个方法
返回类型 | 方法名 |
---|---|
Annotation[] | getAnnotations() |
getAnnotation(Class annotationClass) | |
Annotation[] | getDeclaredAnnotations() |
getDeclaredAnnotation(Class annotationClass) |
它们的用法也很简单
@MyClassAnnotation
public class ReflectTest {
public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException, NoSuchFieldException {
System.out.println(Arrays.toString(ReflectTest.class.getDeclaredAnnotations()));
}
}
//结果
[@com.test.reflectTest.MyClassAnnotation()]
反射父类或接口的Class实例
返回类型 | 方法名 |
---|---|
Class<?>[] | getInterfaces() |
Class<? super T> | getSuperclass() |
通过这两个方法分别能获取该类所实现接口的Class实例数组和所继承的父类Class实例
小结
本章简单介绍了java反射机制,以及其常用API的使用,后续会有深入理解java反射机制的文章推出。