反射(Reflect)
定义
是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意方法和属性;这种动态获取信息以及动态调用对象方法的功能称为java语言的反射机制。
主要的类
类名 | 用途 |
---|---|
Class类 | 代表类的实体,在运行的Java应用程序中表示类和接口 |
Field类 | 代表类的成员变量(成员变量也称为类的属性) |
Method类 | 代表类的方法 |
Constructor类 | 代表类的构造方法 |
个人理解
反射作为Java的一种特殊的调用方法,单独使用一般只是为了调用对象的私有方法等,作用不是很大,也不建议这样使用。
但是反射结合泛型,注解等就可以爆发出强大的功能,比如 Gson的 泛型+反射 实现反序列化,Retrofit 的 动态代理+反射+注解,实现网络请求接口化。
所以在上面反射的主要类中,获取泛型、注解的 API 占了一半左右,如果需要用好反射,必须先精通泛型、注解。
如何学习
- 了解反射java.lang.reflect 包下的接口结构树,从接口入手学习。
- 因为反射涉及的API很多,所以具体使用还是得看的API,多敲敲代码看看各个方法实现了什么功能。
- 再看看各种框架中是如何使用反射的,一般都是结合了泛型、注解。
反射API
以下是JDK 16 的 Doc文档中的java.lang.reflect 包的接口结构树
Type 接口
Type is the common superinterface for all types in the Java programming language. These include raw types, parameterized types, array types, type variables and primitive types. Type是Java编程语言中所有类型的通用超接口。这些类型包括原始类型、参数化类型、数组类型、类型变量和基本类型。
实现子类:
- Class
: 太熟悉了,类的类型,不多解释
继承Type的接口
- ParameterizedType :参数化类型 ,
Collection<String>
之类 - TypeVariable :类型变量,
T,K
之类 - GenericArrayType :数组类型,可以是上面2种类型的数组,
Collection<String> []
,T[]
- WildcardType
: 通配符类型 ? extends T
这类
Member 接口
实现该接口的4个子类 Class, Field, Method, Constructor 都是反射中最常用的
方法说明:
- Class<?> getDeclaringClass() : 返回的是获取到该Member的Class对象,与 getClass不同,(比如:返回的是具体类 com.xxd.reflect.basic.ReflectEntity,而不是 java.lang.reflect.Constructor )
- String getName() : 返回的是 getDeclaringClass().getName()
- int getModifiers() : 返回int类型,需要 & 来判断
- boolean isSynthetic() : 是否为了生成的方法,当前我知道的只有桥方法
AnnotatedElement 接口
- JDK 5 添加进来的方法有4个
boolean isAnnotationPresent(Class<? extends Annotation> annotationClass)
:检测当前上是否有某种Annotation<T extends Annotation> T getAnnotation(Class<T> annotationClass)
:获取某种注解Annotation[] getAnnotations()
:获取当前上的所有注解Annotation[] getDeclaredAnnotations()
:获取当前上的所有注解,不包含继承类的注解
- JDK 8 添加进来的方法有3个
<T extends Annotation> T getDeclaredAnnotation(Class<T> annotationClass)
:获取某种注解,不包含继承<T extends Annotation> T[] getAnnotationsByType(Class<T> annotationClass)
: 与@Repeatable相关,获取到所有可以关联到某种注解的注解<T extends Annotation> T[] getDeclaredAnnotationsByType(Class<T> annotationClass)
: 与@Repeatable相关,获取到所有可以关联到某种注解的注解,不包含继承
在 AnnotatedElement 接口中的方法,主要区分就是2点
- 是否 Declared,表示不能获取到继承的注解,在JDK 14 之前,只有类注解可以继承,其它都不行。
- 是否 ByType ,表示可以获取到关联的注解,什么是关联?涉及到一个元注解 @Repeatable
@Repeatable
可重复注释类型:用于指示它(元)注释的声明的注释类型是可重复的。@Repeatable的值表示可重复注释类型的包含注释类型。
// 可以重复使用的注解
@Target({ElementType.TYPE, ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER, ElementType.CONSTRUCTOR})
@Retention(RetentionPolicy.RUNTIME)
@Repeatable(Ints.class)
public @interface IntObtain {
int value();
}
// 重复使用的注解会被装箱成该注解
@Target({ElementType.TYPE,ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
public @interface Ints {
IntObtain[] value();
}
@interface IntObtain
:可以重复使用的注解,以下简称注解A@interface Ints
: 接收注解A[] 为 value 返回值得注解,以下简称注解B
使用注意事项:
- 定义一个注解B,使用默认函数 value(不能是其它方法名),返回值是A的数组
- 注解A上必须加上
@Repeatable(B.class)
标签,表示该注解重复使用会封装成B注解 - 注解A的target 范围必须大于注解B,因为注解A可以单独使用,而B不能
- 当使用1个注解A,不会自动装箱成B注解,而使用>=2个注解A则会自动装箱成注解B
- 接收的地方使用 ByType方法 ```java // 使用注解的地方 @IntObtain(52) @IntObtain(323) private int a;
// 测试2个不同的获取注解方法 IntObtain a1 = field.getAnnotation(IntObtain.class); IntObtain[] arr1 = field.getAnnotationsByType(IntObtain.class);
打印结果:
> getAnnotation (IntObtain.class) : a1=null
> getAnnotationsByType (IntObtain.class) : arr1.length= 2
<a name="gixU0"></a>
#### GenericDeclaration 接口
所有能定义泛型的地方都需要实现的接口,能定义泛型的地方只有3个,接口、类、方法<br />所以此接口的常用子类只有Class、Constructor、Method<br />`TypeVariable<?>[] getTypeParameters()` :获取所有的泛型定义处的 类型参数<br />`TypeVariable<D extends GenericDeclaration>`是一个有意思的类,它又通过泛型 D 获取定义此泛型的具体位置(就是上面那3个位置)<br />**tips: **所以能获取到TypeVariable的地方,都能获取到GenericDeclaration 的具体类型,Field上的T只能获取到接口类型,但是可以强转
<a name="EcHYV"></a>
#### AnnotatedType 接口
上面的接口已经解决了大部分问题,该有的泛型、注解都能获取到了<br />但是有一个地方的注解无法获取到,下面这种 `@Target(ElementType.TYPE_USE)`类型的注解可以定义在很多奇怪的地方,现有接口无法获取到
```java
private @DoubleObtain(1.1d) List<@DoubleObtain(1.2d) ? extends @DoubleObtain(1.2d) T> @DoubleObtain(1.3d) [] list;
仔细看上面泛型的位置,好像与Type的4个子接口位置是对应的!!
因为@Target(ElementType.TYPE_USE)
接口是JDK 1.8定义的,所以该 AnnotatedType
接口也是 JDK 1.8 定义的,就是用来获取这些奇怪位置的注解。
tips: 只有定义成ElementType.TYPE_USE
的注解才能被 AnnotatedType
找到,其它的注解放在方法前面也找不到,下面是一个例子说明
@Target({ElementType.TYPE, ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER, ElementType.CONSTRUCTOR,ElementType.TYPE_PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
@Repeatable(Ints.class)
public @interface IntObtain { // 这是一个没有 ElementType.TYPE_USE 的注解
int value();
}
@Target(ElementType.TYPE_USE)
@Retention(RetentionPolicy.RUNTIME)
public @interface DoubleObtain { // 这是一个ElementType.TYPE_USE 的注解
double value();
}
public void test(@IntObtain(110) @DoubleObtain(3.14) int a) { }
// 打印普通注解
Annotation[][] parameterAnnotations = method.getParameterAnnotations(); [@com.xxd.reflect.basic.domain.IntObtain(110)] 结论:找不到 ElementType.TYPE_USE 类型的注解,可以找到其它注解// 打印 AnnotatedType 注解 AnnotatedType[] annotatedParameterTypes = method.getAnnotatedParameterTypes(); [@com.xxd.reflect.basic.domain.DoubleObtain(3.14)] 结论:只能找到 ElementType.TYPE_USE 类型的注解,找不到其它注解
反射的使用
Class<?>的获取
- object.getClass()
- Object.class
- Class.forName(…)
Class类型注意事项
int.class != Integer.class
, 基本类型8种都是 Primitive ,对应的是int.class == Integer.TYPE
- int.class != int[].class , 数组是包装类型,每多一层就不同
- 数组创建使用
java.lang.reflect.Array
类,里面有很多关于数组的创建,赋值方法 - Array 创建的数组,除了8种基本数据类型赋值为默认值,其它的数组最后一层都是 null,下面是一个说明的例子
Object o = Array.newInstance(aClass1, 2, 3, 5);
与普通数组相似,数组类型的最后一层是真实类型了,需要初始化。而 Object[] 这类本身就是一个类型,是确定的类型,所以只有最后一层null
Gson到底做了啥
Gson是一个google出品的json格式字符串序列化、反序列化框架,它就是与反射相关。
那么Gson到底做了啥?
- 可以反序列化数组吗?int[] ? int[][]?
- 可以反序列化没有空参构造函数的类吗?
- 可以反序列化接口吗?抽象类呢?
- 在对于泛型的反序列化上,到底是怎么处理的?
问题1 :数组的反序列化
通过序列化一个数组,得到了如下json字符串
{"arr":[3,5,1]}
{"arr":[[312,64,13],[3,7,1]]}
反序列化是成功的,这还是自己对数组类型不够了解才产生了理解的困惑。int[][][] ints = new int[2][][]; // 可以new
一个数组只有最外层需要定长 [2],而内部可以不定长 [ ],原因是数组类型本身就是一个类型,如 int[].class 本身就是一个类(不是 int.class),所以 int[].class 可以为 null。
具体来说 int[2][][] 表示的是 int[][].class 这种类型(以下记为 target.class), 就是由 2个 target.class组成了一个数组,所以2是必须定义的。至于target.class,本身怎么定义,是另外的事。
int[2][3][4] 这样是一种简便的创建方式,等于 target.class = int[3][4]
所以在反序列化数组的时候,不应该通过 Array.newInstance(aClass1, 2, 3, 5)
这种方式来创建,因为内部结构可能是不定长的。只能一层一层的创建,逐个解析。只要到达非数组类型的时候,值可以转为int,就不会报错。
问题2 :没有空参构造函数的反序列化
Gson是可以反序列化没有空参构造函数,并且不走其它构造函数。(如果有null参数构造函数,会走此构造函数)
反射构造一个类是必须要走构造函数的 (不走构造函数的class创建只有 clone, serializable,参考 java 对象创造的 5 中方式)
所以Gson反序列化一个类不是靠反射的,反射只是一个兜底机制,如果其它构件都不成功,最后走反射!
问题遗留:Gson到底是怎么创建一个对象的?创建一个具体类为什么可以没有 no-args的构造函数呢?
已经解决:参考附录
内容1:LinkedTreeMap 类,没有匹配到的类会被反射成此类
问题3 :接口、抽象类的反序列化
接口反序列化失败:Unable to invoke no-args constructor for interface com.xxd.reflect.gson.domain.IGun. Registering an InstanceCreator with Gson for this type may fix this problem.
找不到一个 no-args 的构造函数
抽象类反序列化也失败:
找不到一个 no-args 的构造函数
结论:接口、抽象类在处理在添加单独的 InstanceCreator 前无法反序列化。
问题4 :泛型的反序列化
泛型的擦除机制使得 Class 对象本身拿不到自己的实际泛型 signature 字段,所以直接用 .Class 处理某个json字符串,匹配不上的时候,会生成一个 LinkedTreeMap
类,不断的嵌套自己,使得 Gson在处理反序列化的问题时数据不会丢失。
子类记录了父类泛型的实际 signature 字段,所以Gson 使用了一个Type type = new TypeToken<ClassT<ConcreteEntity>>()
{}
.getType()
的方法,让使用者必须创建子类,所以能拿到对应 signature 字段,就匹配了具体的 Class。
使用API : getGenericSuperclass()
可以获取到父类的Type,带有真实泛型,自己 new 子类,然后调用此API 。与new TypeToken<…>{ }.getType 等价。
手撸Retrofit
Retrofit2作为一个Android最常用的网络框架,使用的是 动态代理+注解+反射,同时适配了很多转换器 Gson , Rxjava 之类,真正用来网络请求的是 okHttp,当然作为代理,也可以换成其它网络框架,只要写一个适配器就可以了。
代码基本无难点,写了一个试试,一遍跑起来。
附录
Gson如何创建一个 没有空参数的对象
参考了Gson源码, com.google.gson.internal.UnsafeAllocator
,使用的是此类来创建的。
Do sneaky things to allocate objects without invoking their constructors. 偷偷摸摸的分配对象内存,而不调用他们的构造函数
以上是UnsafeAllocator的说明,果然还是自己对java理解不深,以为java对象只有5种创建方式。
这里使用的是非安全创建方式,共有3种
- 使用
Class.forName("sun.misc.Unsafe")
,这是sun公司没有开源的API,需要手动分配内存,释放内存,无法GC,可以实现许多功能 - 使用
ObjectStreamClass.class.getDeclaredMethod("getConstructorId", Class.class);
,到内存中找是否已经构件过此类 - 使用
ObjectInputStream.class .getDeclaredMethod("newInstance", Class.class, Class.class);
,和上一个方法实现功能相同
这些方法都可以不调用构造函数创建对象。