29.Android架构-注解与反射

注解的作用或者意义是什么?

注解本身没有任何意义,单独的注解就是一种注释,他需要结合其他如反射、插桩等技术才有意义。

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元数据中。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,816评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,729评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 158,300评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,780评论 1 285
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,890评论 6 385
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,084评论 1 291
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,151评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,912评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,355评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,666评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,809评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,504评论 4 334
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,150评论 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,882评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,121评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,628评论 2 362
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,724评论 2 351