注解的作用或者意义是什么?
注解本身没有任何意义,单独的注解就是一种注释,他需要结合其他如反射、插桩等技术才有意义。
Java 注解(Annotation)又称 Java 标注,是 JDK1.5 引入的一种注释机制。是元数据的一种形式,提供有关于程序但不属于程序本身的数据。注解对它们注解的代码的操作没有直接影响。
注解保留级别@Retention
RetentionPolicy.SOURCE
标记的注解仅保留在源码级别中,并被编译器忽略。
RetentionPolicy.CLASS
标记的注解在编译时由编译器保留,但 Java 虚拟机(JVM)会忽略。
RetentionPolicy.RUNTIME
标记的注解由 JVM 保留,因此运行时环境可以使用它。
SOURCE < CLASS < RUNTIME,即CLASS包含了SOURCE,RUNTIME包含SOURCE、CLASS
不同保留级别注解的应用场景
1.源码级别 ( APT技术(Anotation Processor Tools) )
在编译期能够获取注解与注解声明的类包括类中所有成员信息,一般用于生成额外的辅助类。源码级别的注解,可提供给IDE语法检查(@Target({ANNOTATION_TYPE}))、APT等场景使用如ButterKnife
,在类中使用 SOURCE 级别的注解,其编译之后的class中会被丢弃。
注解处理器是 javac 自带的一个工
具,用来在编译时期扫描处理注解信息。你可以为某些注解注册自己的注解处理器。 注册的注解处理器由 javac调起,并将注解信息传递给注解处理器进行处理。注解处理器是对注解应用最为广泛的场景。在Glide、EventBus3、Butterknifer、Tinker、ARouter等等常用框架中都有注解处理器的身影。但是你可能会发现,这些框架中对注解的定义并不是 SOURCE 级别,更多的是 CLASS 级别,别忘了:CLASS包含了SOURCE,RUNTIME包含SOURCE、CLASS。
2.字节码级别 (字节码增强技术)
在编译出Class后,通过修改Class数据以实现修改代码逻辑目的。对于是否需要修改的区分或者修改为不同逻辑的判断可以使用注解。如插桩
定义为 CLASS 的注解,会保留在class文件中,但是会被虚拟机忽略(即无法在运行期反射获取注解)。此时完全符合此种注解的应用场景为字节码操作。如:AspectJ、热修复Roubust中应用此场景。所谓字节码操作即为,直接修改字节码Class文件以达到修改代码执行逻辑的目的。
3.运行时级别 (反射技术)
注解保留至运行期,意味着我们能够在运行期间结合反射技术获取注解中的所有信息。
在程序运行期间,通过反射技术动态获取注解与其元素,从而完成不同的逻辑判定。如一些比较古老的框架在运行时通过反射进行view的findViewById操作,或者Gson中也使用到了这个能力
反射
反射为什么比较耗性能?
1、Method#invoke 需要进行自动拆装箱
invoke 方法的参数是 Object[] 类型,如果是基本数据类型会转化为Integer装箱,同时再包装成Object数组。在执行时候又会把数组拆解开,并拆箱为基本数据类型。
2、反射需要按名检索类和方法
getDeclaredField() getDeclaredMethod() 在底层都需要循环遍历所有的属性和方法去匹配得到对应的Field和Method
http://androidxref.com/9.0.0_r3/xref/art/runtime/mirror/class.cc#1265
3、需要检查方法
setAccessible,反射时需要检查方法可见性以及每个实际参数与形式参数的类型匹配性
4、编译器无法对动态调用的代码做优化,比如内联
反射涉及到动态解析的类型,影响内联判断并且无法进行JIT
内联:函数调用除了执行自身逻辑的开销外,还有一些不为人知的额外开销。这部分额外的开销主要来自方法栈帧的生成、参数字段的压栈、栈帧的弹出、还有指令执行地址的跳转,尤其对于递归方法来说,可能方法体执行消耗的时间还没有这些操作消耗的多,所以编译器会在变异阶段通过分析,确定是否将满足条件的方法直接展开从而消除函数调用,这种行为就是内联,以提高效率
,如下例子,就是内联的结果
//内联之前
public int add(int a, int b , int c, int d){
return add(a, b) + add(c, d);
}
public int add(int a, int b){
return a + b;
}
//内联之后
public int add(int a, int b , int c, int d){
return a + b + c + d;
}
反射就是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意方法和属性;并且能改变它的属性。是Java被视为动态语言的关键。
反射如何获取泛型的真实类型?
当我们对一个泛型类进行反射时,需要得到泛型中的真实数据类型,来完成如json反序列化的操作。此时需要通过 Type 体系
来完成。 Type 接口
包含了一个实现类(Class)和四个实现接口,他们分别是:
TypeVariable
泛型类型变量。可以泛型上下限等信息;
public class TestFanxing<K extends Comparable & Serializable, V> {
K key;
V value;
public static void main(String[] args) throws Exception {
// 获取字段的类型
Field fk = TestFanxing.class.getDeclaredField("key");
Field fv = TestFanxing.class.getDeclaredField("value");
TypeVariable keyType = (TypeVariable) fk.getGenericType();
TypeVariable valueType = (TypeVariable) fv.getGenericType();
// getName 方法
System.out.println(keyType.getName());
// K
System.out.println(valueType.getName());
// V // getGenericDeclaration 方法
System.out.println(keyType.getGenericDeclaration());
// class com.test.TestType
System.out.println(valueType.getGenericDeclaration());
// class com.test.TestType // getBounds 方法
System.out.println("K 的上界:");
// 有两个
for (Type type : keyType.getBounds()) {
// interface java.lang.Comparable
System.out.println(type);
// interface java.io.Serializable
}
System.out.println("V 的上界:");
// 没明确声明上界的, 默认上界是 Object
for (Type type : valueType.getBounds()) {
// class java.lang.Object
System.out.println(type);
}
}
}
打印结果:
K
V
class TestFanxing
class TestFanxing
K 的上界:
interface java.lang.Comparable
interface java.io.Serializable
V 的上界:
class java.lang.Object
ParameterizedType
具体的泛型类型,可以获得元数据中泛型签名类型(泛型真实类型)
public class TestFanxing<K extends Comparable & Serializable, V> {
Map<String, String> map;
public static void main(String[] args) throws Exception {
Field f = TestFanxing.class.getDeclaredField("map");
System.out.println(f.getGenericType());
// java.util.Map<java.lang.String, java.lang.String>
ParameterizedType pType = (ParameterizedType) f.getGenericType();
System.out.println(pType.getRawType());
// interface java.util.Map
for (Type type : pType.getActualTypeArguments()) {
System.out.println(type);
// 打印两遍: class java.lang.String
}
}
}
java.util.Map<java.lang.String, java.lang.String>
interface java.util.Map
class java.lang.String
class java.lang.String
GenericArrayType
当需要描述的类型是泛型类的数组时,比如List[],Map[],此接口会作为Type的实现。
public class TestFanxing<K extends Comparable & Serializable, V> {
List<String>[] lists;
public static void main(String[] args) throws Exception {
Field f = TestFanxing.class.getDeclaredField("lists");
GenericArrayType genericType = (GenericArrayType) f.getGenericType();
System.out.println(genericType.getGenericComponentType());
}
}
java.util.List<java.lang.String>
WildcardType
通配符泛型,获得上下限信息;
public class TestFanxing<K extends Comparable & Serializable, V> {
private List<? extends Number> a;
private List<? super String> b;
public static void main(String[] args) throws Exception {
Field fieldA = TestFanxing.class.getDeclaredField("a");
Field fieldB = TestFanxing.class.getDeclaredField("b");
//先拿到范型类型
ParameterizedType pTypeA = (ParameterizedType) fieldA.getGenericType();
ParameterizedType pTypeB = (ParameterizedType) fieldB.getGenericType();
//再从范型里拿到通配符类型
WildcardType wTypeA = (WildcardType) pTypeA.getActualTypeArguments()[0];
WildcardType wTypeB = (WildcardType) pTypeB.getActualTypeArguments()[0];
//方法测试
System.out.println(wTypeA.getUpperBounds()[0]);
//class java.lang.Number
System.out.println(wTypeB.getLowerBounds()[0]);
//class java.lang.String 看看通配符类型到底是什么, 打印结果为: ? extends java.lang.Number
System.out.println(wTypeA);
}
}
class java.lang.Number
class java.lang.String
? extends java.lang.Number
Gson反序列化
static class Response<T> {
T data;
int code;
String message;
@Override
public String toString() {
return "Response{" + "data=" + data + ", code=" + code + ", message='" + message + '\'' + '}';
}
public Response(T data, int code, String message) {
this.data = data;
this.code = code;
this.message = message;
}
}
static class Data {
String result;
public Data(String result) {
this.result = result;
}
@Override
public String toString() {
return "Data{" + "result=" + result + '}';
}
}
public static void main(String[] args) {
Response<Data> dataResponse = new Response(new Data("数据"), 1, "成功");
Gson gson = new Gson();
String json = gson.toJson(dataResponse);
System.out.println(json);
//为什么TypeToken要定义为抽象类?
Response<Data> resp = gson.fromJson(json, new TypeToken<Response<Data>>() {
}.getType());
System.out.println(resp.data.result);
}
在进行GSON反序列化时,存在泛型时,可以借助 TypeToken 获取Type以完成泛型的反序列化。但是为什么
TypeToken 要被定义为抽象类呢?
因为只有定义为抽象类或者接口,这样在使用时,需要创建对应的实现类,此时确定泛型类型,编译才能够将泛型
signature信息记录到Class元数据中。