java注解小结

注解,英文,Annotation,也称为:元数据。java, javafx, android都内置了很多注解。
稍微来看几个:

@Documented
@Retention(CLASS)
@Target({METHOD, PARAMETER, FIELD, LOCAL_VARIABLE, ANNOTATION_TYPE, PACKAGE})
public @interface NonNull {  //package android.support.annotation; android支持包中的
}

@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.FIELD, ElementType.METHOD})
public @interface FXML {
}

@Target({ElementType.METHOD})
@Retention(RetentionPolicy.SOURCE)
public @interface Override {
}

比如最常见的接口、类继承的时候,Override的方法会提示报错,这是因为编译器发出了错误警告。而IDE发扬光大在显示的时候直接红色。

一。使用注解

这个就不多做解释了。后面介绍target就会有讲。并且我们也经常看到各种,Override,Deprecated,相信都有接触。

二。定义注解

第一步,跟定义接口是一样的,只是多了一个@。

public @interface StartupEntro {
}

第二步,需要描述2个必须的“头”。在头上添加2个meta annotation(元注解),

  • @Target来指明注解用在哪里,可以逗号隔开多个,如前面NonNull;
  • @Retention指明保留在什么级别,对应着不同的工作模式。

其实有4个元注解可以放在一个注解的头部,分别是

@Target 表示该注解用于什么地方,可能的值在枚举类 ElemenetType 中,包括:

 ElemenetType.CONSTRUCTOR-----------------------------构造器声明 
 ElemenetType.FIELD ----------------------------------域声明(包括 enum 实例) 
 ElemenetType.LOCAL_VARIABLE------------------------- 局部变量声明 
 ElemenetType.METHOD ---------------------------------方法声明 
 ElemenetType.PACKAGE --------------------------------包声明 
 ElemenetType.PARAMETER ------------------------------参数声明 
 ElemenetType.TYPE----------------------------------- 类,接口(包括注解类型)或enum声明 

@Retention 表示在什么级别保存该注解信息。可选的参数值在枚举类型 RetentionPolicy 中,包括:

 RetentionPolicy.SOURCE-------------注解将被编译器丢弃 
 RetentionPolicy.CLASS -------------注解在class文件中可用,但会被VM丢弃 
 RetentionPolicy.RUNTIME ---------VM将在运行期也保留注释,因此可以通过反射机制读取注解的信息。

@Documented 将此注解包含在 javadoc 中 ,它代表着此注解会被javadoc工具提取成文档。在doc文档中的内容会因为此注解的信息内容不同而不同。相当与@see,@param 等。

@Inherited 允许子类继承父类中的注解。

后2个这边不做介绍,忽略。也非必须。

因此,你定义的这个注解想要用在什么地方倒是好处理,比如我要定义参数的就描述@Target({PARAMETER});又比如,我要标记某个模块的类,想被其他模块调用,就用@Target({TYPE})

@Target({ElementType.TYPE})
public @interface StartupEntro { //TODO 还缺少Retention
}

还得使用 @Retention 指明保留在什么级别,对应着不同的工作模式。

我们还得一一来研究着3种RetentionPolicy。

三。1. Retention - RetentionPolicy.SOURCE
@Retention(RetentionPolicy.SOURCE)
@Target({ElementType.TYPE})
public @interface StartupEntro {
}

你看,当我们指明了Target只有TYPE,表示只标注类,放在方法上面就会IDE提示报错。(不用担心搞错啦。大胆尝试呗!)只保留类的描述。

源码注解(RetentionPolicy.SOURCE)的生命周期只存在Java源文件这一阶段,是3种生命周期中最短的注解。当在Java源程序上加了一个注解,这个Java源程序要由javac去编译,javac把java源文件编译成.class文件,在编译成class时会把Java源程序上的源码注解给去掉。

你看android support包中有一个IntDef:

@Retention(SOURCE)
@Target({ANNOTATION_TYPE})
public @interface IntDef {
    /** Defines the allowed constants for this element */
    int[] value() default {};

    /** Defines whether the constants can be used as a flag, or just as an enum (the default) */
    boolean flag() default false;
}

每一个枚举值都是一个对象,在使用它时会增加额外的内存消耗,所以枚举相比与 Integer 和 String 会占用更多的内存。 较多的使用 Enum 会增加 DEX 文件的大小,会造成运行时更多的开销。单个枚举会使应用的 classes.dex 文件增加大约 1.0 到 1.4 KB 的大小。@IntDef,它的目的是为了限定某些类似Status状态机的int值取值的范围,因为如果直接写比如:

public static class BTStatus {
    public static final int STATUS_ADLE = 1;
    public static final int STATUS_OPENING = 2;
    public static final int STATUS_WORKED = 3;
    public static final int STATUS_CLOSING = 4;
}

那么,我们用的时候setStatus(int st),容易给出5,6,-1,-100去都是可以的。

所以我们结合IntDef,再定义出一个注解为:

@Retention(RetentionPolicy.SOURCE) //源码级别,class文件是没有的。
@Target(ElementType.PARAMETER) //参数上去使用它
@IntDef({BTStatus.STATUS_ADLE, BTStatus.STATUS_OPENING, BTStatus.STATUS_CLOSING, BTStatus.STATUS_WORKED}) //范围指定了
public @interface MyStatus {
    int STATUS_ADLE = 1;
    int STATUS_OPENING = 2;
    int STATUS_WORKED = 3;
    int STATUS_CLOSING = 4;
}

[(img-1hDPh3FK-1621503199772)(/Users/allan/Library/Application Support/typora-user-images/image-20210520150841609.png)]

你看,这就提示错误了。

同样的,我将IntDef拷贝到IDEA的java工程中就不会提示出错了。而且kotlin的老版本似乎也不支持(最新有待调研)。推测是android studio做了兼容support包的逻辑。可见他的SOURCE级别的含义就在此处,编译后就丢弃掉了,用做范围取值合适不过。同理还有StringDef

三。2. Retention - RetentionPolicy.CLASS

保留在字节码阶段,VM阶段就没有了。即编译阶段有用。这就牵涉到了APT,注解处理器

编译时注解的核心就是实现AbstractProcessor的process()方法,一般来说主要有以下两个步骤
1。搜集信息,包括被注解的类的类信息,方法,字段等信息,还有注解的值;
2。生成对应的java源代码,主要根据上一步的信息,生成响应的代码。

下图表明RetentionPolicy.CLASS的会被保留在class反编译的文件中:而SOURCE则不会。 在这里插入图片描述

这种方法,在android各大框架中广泛使用。用来节省同样可以实现类似功能的方式“反射”的开销

TODO 这部分还需要补充和研究。

有兴趣的就多查阅一些,AbstractProcessor相关的资料。这里就简略的描述一下。想要学好这个APT的使用确实还挺麻烦的。android中还有auto-service生成辅助信息,javapoet来配合生成java文件。具体自行学习了。

java注解之编译时注解RetentionPolicy.CLASS 基本用法

java 秒懂 注解 (Annotation)你可以这样学 运行时注解和编译注解

三。3. Retention - RetentionPolicy.RUNTIME

最后一种生命周期最长。VM将在运行期也保留注释,因此可以通过反射机制读取注解的信息。

这种方式相对CLASS需要编写APT而言简单一些。不过在android中反射会比较耗时,尤其在Application init阶段使用的话,往往是各大公司首先会去优化的点。会将运行时改成编译时的CLASS去解决。不过会复杂一些。在java后台项目(比如Spring大量的反射),pc项目或者android上一些后期的动作,偶尔使用一下动态运行的代码,反射使用,比较合适。当然啦,普通项目中,不在乎不扣这一点性能的也可以使用。慢慢优化即可。

    private Startup.IStartupInit create() {
        var list = ClassUtil.getAllClassByInterface(Startup.IStartupInit.class, "com.tools");
        System.out.println("list" + list.size());
        //自行保证list
        try {
            var clazz = list.get(0);
            Object obj = clazz.getConstructor().newInstance();
            return (Startup.IStartupInit) obj;
        } catch (Throwable e) {
            e.printStackTrace();
        }
        return null;
    }

上述代码是根据反射查找某个接口的子类。如果不是interface的子类,有RUNTIME的注解也可以获取。

var list = ClassUtil.getClasses("com.tools");
//自行判断list
for (Class<?> cls : list) {
if (cls.getAnnotation(StartupEntro.class) != null) {
    //TODO 拿到了目标class
 }
}

不过由于ClassUtil的实现需要指定包名,并且就算实现了不指定包名的方式,也不建议全局搜索,大型工程,你都不知道有成千上万的类,所有包名扫一遍。emmmm…

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值