Java 注解

注解的本质就是给载体打一个Tag,我们查找到这个tag后就可以对我们打Tag的载体进行一些特殊处理

Java 注解(Annotation)又称 Java 标注,是 JDK5.0 引入的一种注释机制。 注解是元数据的一种形式,提供有关 于程序但不属于程序本身的数据。注解对它们注解的代码的操作没有直接影响。

注解声明
声明一个注解类型
Java
中所有的注解,默认实现
Annotation
接口:
package java.lang.annotation;

public interface Annotation {
    boolean equals(Object obj);

    int hashCode();

    String toString();

    Class<? extends Annotation> annotationType();
}

与声明一个"Class"不同的是,注解的声明使用 @interface 关键字。一个注解的声明如下:

public @interface FirstAnnotation {
    String name();

    int age();
}

元注解

在定义注解时,注解类也能够使用其他的注解声明。对注解类型进行注解的注解类,我们称之为 metaannotation(元注解)。一般的,我们在定义自定义注解时,需要指定的元注解有两个 :

另外还有
@Documented

@Inherited
元注解,前者用于被
javadoc
工具提取成文档,
后者表示允许子类继承父类中定义的
注解
,@Repeatable是jdk8中新增的注解在没有@Repeatable元注解的注解中,在同一个地方使用相同的注解会报错,有了此元注解注,既可以在同一个地方使用相同的注解。
@Target

注解标记另一个注解,以限制可以应用注解的
Java
元素类型。目标注解指定以下元素类型之一作为其值:

ElementType.ANNOTATION_TYPE      
可以应用于注解类型。
ElementType.CONSTRUCTOR           
可以应用于构造函数。
ElementType.FIELD                              
可以应用于字段或属性。
ElementType.LOCAL_VARIABLE         
可以应用于局部变量。
ElementType.METHOD                         
可以应用于方法级注解。
ElementType.PACKAGE                       
可以应用于包声明。
ElementType.PARAMETER                  
可以应用于方法的参数。
ElementType.TYPE_USE                     
可以应用于类的任何元素。
ElementType.TYPE.                               可以应用于类上
ElementType.TYPE_PARAMETER       
可以应用于类型变量的声明语句中(eg:泛型声明)

@Retention

注解指定标记注解的存储方式:

RetentionPolicy.SOURCE
-
标记的注解仅保留在源级别中,并被编译器忽略。
RetentionPolicy.CLASS
-
标记的注解在编译时由编译器保留,但
Java
虚拟机
(JVM)
会忽略。
RetentionPolicy.RUNTIME
-
标记的注解由
JVM
保留,因此运行时环境可以使用它。
@Retention
三个值中
SOURCE < CLASS < RUNTIME
,即
CLASS
包含了
SOURCE

RUNTIME
包含
SOURCE
、 CLASS。下文会介绍他们不同的应用场景。

 

下面来看例子:
@Retention(RetentionPolicy.SOURCE)  //只保留在java源码中
@Target({ElementType.FIELD}) //只能作用于属性
public @interface FirstAnnotation {
    String name(); //无默认值
    int age() default 18; // 有默认值
}

注意:在使用注解时,如果定义的注解中的类型元素无默认值,则必须进行传值。

public class AnnotationTest {
    @FirstAnnotation("那路多") //如果只存在value元素需要传值的情况,则可以省略:value =
    Student student; //注意只有命名为value才能省略 value = 直接写
 
    @FirstAnnotation(value = "萨斯给",age = 18)
    Student student1;
}
注解应用场景:
按照
@Retention
元注解定义的注解存储方式,注解可以被在三种场景下使用:

 

SOURCE 级别:

在把java文件编译成Class文件后source级别的注解就会消失:

可以看到编译后source级别的注解消失了

可以用于 IDE检查和APT等场景使用


Android
开发中,
support
-
annotations

androidx.annotation
中均有提供
@IntDef

@DrawableResId
等注 解,此注解的定义如下:
@Retention(SOURCE) //源码级别注解 
@Target({ANNOTATION_TYPE})
public @interface IntDef {
    int[] value() default {};

    boolean flag() default false;

    boolean open() default false;
}

 

Java

Enum(
枚举
)
的实质是特殊单例的静态成员变量,在运行期所有枚举类作为单例,全部加载到内存中。 比常量多5

10
倍的内存占用。
此注解的意义在于能够取代枚举,实现如方法入参限制。
例: 

@IntDef(value = {1,2}) // 限定只能传 1 和 2
@Retention(RetentionPolicy.SOURCE) //只保留在java源码中
@Target({ElementType.PARAMETER}) //只能作用于方法参数之上
public @interface IntDefAnnotationTest {
    int value();
}


public class AnnotationTest {

    public void test(@IntDefAnnotationTest(value = 1) int a){
        System.out.println(a);
    }
}

 如果传1或者2编译器可以通过 

如果传额外的数字例如3
会显示
Inspection
警告

IDE报警了但是还是能编译通过和运行

可以修改此类语法检查级别:

 可以看到AS没有再报错,本身IDEA/AS 就是由Java开发的,Lint工具实现了对Java语法的检查,借助注解能对被注解的特定语法进行额外检查。

2.APT annotation processor tools 注解处理器技术

 

APT
全称为:
"Anotation Processor Tools"
,意为注解处理器。顾名思义,其用于处理注解。编写好的
Java
源文 件,需要经过 javac
的编译,翻译为虚拟机能够加载解析的字节码
Class
文件。注解处理器是
javac
自带的一个工 具,用来在编译时期扫描处理注解信息。你可以为某些注解注册自己的注解处理器。 注册的注解处理器由 javac调起,并将注解信息传递给注解处理器进行处理。
注解处理器是对注解应用最为广泛的场景。在
Glide

EventBus3

Butterknifer

Tinker

ARouter
等等常用 框架中都有注解处理器的身影。但是你可能会发现,这些框架中对注解的定义并不是 SOURCE
级别,更多的是 CLASS
级别,别忘了:
CLASS
包含了
SOURCE

RUNTIME
包含
SOURCE

CLASS

 

关于注解处理器的实现,会在后续:APT注解处理器的实现文章中给出。此处先不进行详细介绍。
CLASS级别:
 字节码增强(插桩)技术
定义为
CLASS
的注解,会保留在
class
文件中,但是会被虚拟机忽略
(
即无法在运行期反射获取注解
)
。此时完全符合 此种注解的应用场景为字节码操作。如:AspectJ
、热修复
Roubust
中应用此场景。 所谓字节码操作即为,直接修改字节码Class
文件以达到修改代码执行逻辑的目的。在程序中有多处需要进行是否登录的判断。

如果我们使用普通的编程方式,需要在代码中进行
if
-
else
的判断,也许存在十个判断点,则需要在每个判断点加 入此项判断。此时,我们可以借助AOP(
面向切面
)
编程思想,将程序中所有功能点划分为:
需要登录

无需登录两种类型,即两个切面。对于切面的区分即可采用注解。

//Java源码
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.CLASS)
public @interface Login {
}

    @Login
    public void jumpA() {
        startActivity(new Intent(this, AActivity.class));
    }

    public void jumpB() {
        startActivity(new Intent(this, BActivity.class));
    }
在上诉代码中,
jumpA
方法需要具备登录身份。而
Login
注解的定义被设置为
CLASS
。因此我们能够在该类所编 译的字节码中获得到方法注解 Login
。在操作字节码时,就能够根据方法是否具备该注解来修改
class
中该方法的内容加入 if
-
else
的代码段:
    //Class字节码
    @Login
    public void jumpA() {
        if (this.isLogin) {
            this.startActivity(new Intent(this, LoginActivity.class));
        } else {
            this.startActivity(new Intent(this, AActivity.class));
        }
    }

    public void jumpB() {
        startActivity(new Intent(this, BActivity.class));
    }
注解能够设置类型元素
(
参数
)
,结合参数能实现更为丰富的场景,如:运行期权限判定等。
RUNTIME :
注解保留至运行期,意味着我们能够在运行期间结合反射技术获取注解中的所有信息。

实现:用反射技术和RUNTIME级别注解,实现ButterKnife功能

@Inherited

作用:如果一个类用上了@Inherited修饰的注解,那么其子类也会继承这个注解

注意是作用在类上的注解切必须是继承关系。

注意:

  1. 接口用上个@Inherited修饰的注解,其实现类不会继承这个注解
  2. 父类的方法用了@Inherited修饰的注解,子类也不会继承这个注解

例:

@Inherited
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE,ElementType.FIELD})
public @interface FirstAnnotation {
    String value();
    int age() default 18;

}
@FirstAnnotation("继承")
public class AnnotationTest {

    @FirstAnnotation("那路多")
    Student student;

    @FirstAnnotation(value = "萨斯给",age = 18)
    Student student1;

    public void test(@IntDefAnnotationTest(value = 1) int a){
        System.out.println(a);
    }
}

public class AnnotationTest1 extends AnnotationTest {



}
public class AnnotationMainTest {
    public static void main(String[] args) {
        System.out.println(Arrays.toString(AnnotationTest1.class.getAnnotations()));
    }
}

执行结果:

 只有作用在类上的继承过来了。

@Repeatable

@Repeatable是jdk8中新增的注解。在没有@Repeatable注解的的注解中,在同一个地方使用相同的注解会报错,有了此元注解注解的注解,就可以在同一个地方使用相同的注解。

官方解释:

The annotation type {@code java.lang.annotation.Repeatable} is used to indicate that the annotation type whose declaration it (meta-)annotates is repeatable. The value of @Repeatable indicates the containing annotation type for the repeatable annotation type.
@Repeatable 注解是用于声明其它类型注解的元注解,来表示这个声明的注解是可重复的。@Repeatable的值是另一个注解,其可以通过这个另一个注解的值来包含这个可重复的注解。

@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE,ElementType.FIELD})
@Repeatable(SecondAnnotation.class)
public @interface FirstAnnotation {
    String value();
    int age() default 18;

}
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE,ElementType.FIELD})
public @interface SecondAnnotation {
    FirstAnnotation[] value();
}

其中,@FirstAnnotation 注解上的元注解@Repeatable中的值,使用了@SecondAnnotation注解,@SecondAnnotation注解中包含的值类型是一个@FirstAnnotation注解的数组!
这就解释了官方文档中@Repeatable中值的使用。

测试: 

public class AnnotationTest {


    @FirstAnnotation("1")
    @FirstAnnotation("2")
    @FirstAnnotation("3")
    int a;

}
public class AnnotationMainTest {
    public static void main(String[] args) {
       Class annotationTestClass= AnnotationTest.class;
        try {
           Field field = annotationTestClass.getDeclaredField("a");
           Annotation[] annotations = field.getAnnotations();
           System.out.println(annotations.length);
           System.out.println(Arrays.toString(annotations));
        } catch (NoSuchFieldException e) {
            e.printStackTrace();
        }
    }
}

执行:

因为int a属性上使用了3个@FirstAnnotation注解,所以猜测打印注解长度为3,然后打印详情,可是结果并不同。

1
[@com.example.fragmentadaptertest.annotation.SecondAnnotation(value=[@com.example.fragmentadaptertest.annotation.FirstAnnotation(age=18, value=1), @com.example.fragmentadaptertest.annotation.FirstAnnotation(age=18, value=2), @com.example.fragmentadaptertest.annotation.FirstAnnotation(age=18, value=3)])]

结果显示,int a属性上的注解长度为 1 , 且打印信息为@SecondAnnontation注解,它的值包含了使用的两个注解。因此可知在jdk8中,相同注解只是以集合的方式进行了保存,原理并没有变化。

注意事项

一些约束

@Repeatable 所声明的注解,其元注解@Target的使用范围要比@Repeatable的值声明的注解中的@Target的范围要大或相同,否则编译器错误,显示@Repeatable值所声明的注解的元注解@Target不是@Repeatable声明的注解的@Target的子集

@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE,ElementType.FIELD})
@Repeatable(SecondAnnotation.class)
public @interface FirstAnnotation {
    String value();
    int age() default 18;

}
@Retention(RetentionPolicy.SOURCE)
@Target({ElementType.TYPE,ElementType.FIELD})
public @interface SecondAnnotation {
    FirstAnnotation[] value();
}

简单说就是如果想让一个自定义注解A可以在一个地方使用,就打上@Repeatable标签,标签的值是一个注解B,注解B的value的类型必须是A的数组类型。

最后使用多个A注解的实质,就是一个B注解类型包裹多个A注解类型的值。

想一想供需关系,A需要B来包裹实现注解数组,我们使用的是A在一个地方多次使用,就知道A在B必须在,A不在B可以在也可以不在。

所以B的存在级别要大于等于A。

RUNTIME > CLASS > SOURCE

本图文内容来源于网友网络收集整理提供,作为学习参考使用,版权属于原作者。
THE END
分享
二维码
< <上一篇
下一篇>>