初谈一Java Annotation

    xiaoxiao2021-03-26  27

    原文:点击打开链接

    我相信 Java 注解大家都不会陌生,许多开源的第三方框架中都有它的身影如:butterknife,eventbus,retrofit2, dagger2 等等…有这样一个细节你是否注意到了呢?在 Activity 中重写 onCreate 方法:

    @Override //标记注解 protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); } 1234 1234

    @Override 这里就用到了非常常见的 标记注解,我们继续看看 Override 注解:

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

    @Retention ,@Target 又分别有什么含义呢?请继续往后面看。

    什么是注解

    注解(Annotation),也叫(metadata)元数据。一种代码级别的说明。它是JDK1.5及以后版本引入的一个特性,与类、接口、枚举是在同一个层次。它可以声明在包、类、字段、方法、局部变量、方法参数等的前面,用来对这些元素进行说明,注释。这些信息被存储在Annotation的“name=value”结构对中。

    那么你可能又有疑问什么是(metadata)元数据呢?

    这个解释起来比较抽象,我的理解是:“数据的数据”。

    那它有什么作用呢,如果要对于元数据的作用进行分类,还没有明确的定义,不过我们可以根据它所起的作用,大致可分为三类:

    编写文档:通过代码里标识的元数据生成文档。

    代码分析:通过代码里标识的元数据对代码进行分析。

    编译检查:通过代码里标识的元数据让编译器能实现基本的编译检查

    注解的分类

    根据注解的参数个数分类:

    标记注解。一个没有成员的 Annotation,这种类型仅仅使用自身的存在与否来为我们提供信息。标记注解非常常见,比如上面所说的 @Override

    单值注解。成员的参数为单个参数

    完整注解。成员的参数为多个参数

    根据注解使用的方法和用途分类:

    JDK内置系统注解

    元注解

    自定义注解

    它们都不会直接影响到程序的语义,只是作为注解(标识)存在,我们可以通过反射机制编程实现对这些元数据(用来描述数据的数据)的访问。另外,你可以在编译时选择代码里的注解是否只存在于源代码级,或者它也能在class文件、或者运行时中出现(SOURCE/CLASS/RUNTIME)。下面我具体根据注解使用的方法和用途分类来讲解。

    内置注解

    内置注解分为三类:

    @Override

    @Deprecated

    @SuppressWarnings

    1、@Override

    它的作用是对覆盖超类中方法的方法进行标记,如果被标记的方法并没有实际覆盖超类中的方法,则编译器会发出错误警告。换句话理解就是重写父类方法,方法前的标记。

    @Override protected void onStart() { super.onStart(); } 1234 1234
    2、@Deprecated

    它的作用是对不应该再使用的方法添加注解,当编程人员使用这些方法时,将会在编译时显示提示信息,它与javadoc里的@deprecated标记有相同的功能。

    public class User { @Deprecated public static String getName(){ return "user"; } } 12345678 12345678

    调用 getName 方法:

    public class MainActivity extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); //@Deprecated 方法已经过时 出现中划线 User.getName(); } } 123456789101112 123456789101112
    3、@SuppressWarnings

    其参数有:

    deprecation 使用了过时的类或方法时的警告unchecked 执行了未检查的转换时的警告fallthrough 当 switch 程序块直接通往下一种情况而没有 break 时的警告path 在类路径、源文件路径等中有不存在的路径时的警告serial 当在可序列化的类上缺少serialVersionUID 定义时的警告finally 任何 finally 子句不能正常完成时的警告all 关于以上所有情况的警告 public class User { @SuppressWarnings("deprecation") public static int getAge() { return 18; } } 12345678 12345678

    元注解

    元注解就是定义注解的注解,由 java API 提供,分别有四个元注解:

    @Target

    @Retention

    @Documented

    @Inherited

    1、@Target

    用于描述注解的使用范围。修饰的对象范围:packages、types(类、接口、枚举、Annotation类型)、类型成员(方法、构造方法、成员变量、枚举值)、方法参数和本地变量(如 catch 参数)。

    它的值在枚举类 ElemenetType 中:

    CONSTRUCTOR: 用于描述构造器FIELD : 用于描述域LOCAL_VARIABLE: 用于描述局部变量METHOD : 用于描述方法PACKAGE : 用于描述包PARAMETER : 用于描述参数TYPE : 用于描述类、接口(包括注解类型) 或enum声明

    Mode类可以注解类的成员变量:

    @Target(ElementType.FIELD) @Documented public @interface Mode { public int value() default 0; } 12345 12345

    Person可以注解类、接口(包括注解类型)、或者enum声明:

    @Target(ElementType.TYPE) public @interface People { public String value() default ""; } 1234 1234
    2、@Retention

    表示需要在什么级别保存该注释信息,用于描述注解的生命周期(即:被描述的注解在什么范围内有效),定义了该Annotation被保留的时间长短:某些Annotation仅出现在源代码中,而被编译器丢弃;而另一些却被编译在class文件中;编译在class文件中的Annotation可能会被虚拟机忽略,而另一些在class被装载时将被读取。

    RetentionPoicy取值:

    SOURCE : 在源文件中有效(即源文件保留)CLASS : 在class文件中有效(即class保留)RUNTIME : 在运行时有效(即运行时保留)

    Page 注解的RetentionPolicy 的值为 RUNTIME,这样注解处理器可以通过反射,获取到该注解的属性,从而做一些运行时的逻辑处理。

    @Target(ElementType.FIELD) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface Page { public int value() default 1; } 123456 123456
    3、@Documented

    用于描述其它类型的annotation应该被作为被标注的程序成员的公共API,Documented 是一个标记注解,没有成员。将此注解包含在 javadoc 中 ,它代表着此注解会被javadoc工具提取成文档。在doc文档中的内容会因为此注解的信息内容不同而不同。

    @Documented public @interface Page { public int value() default 0; } 1234 1234
    4、@Inherited

    允许子类继承父类中的注解,是一个标记注解,@Inherited阐述了某个被标注的类型是被继承的。如果一个使用了@Inherited修饰的annotation类型被用于一个class,则这个annotation将被用于该class的子类。

    @Inherited annotation类型是被标注过的class的子类所继承。类并不从它所实现的接口继承annotation,方法并不从它所重载的方法继承annotation

    当@Inherited annotation类型标注的annotation的Retention是RetentionPolicy.RUNTIME,则反射API增强了这种继承性。如果我们使用java.lang.reflect去查询一个@Inherited annotation类型的annotation时,反射代码检查将展开工作:检查class和其父类,直到发现指定的annotation类型被发现,或者到达类继承结构的顶层。

    自定义注解

    使用@interface自定义注解时,自动继承了java.lang.annotation.Annotation接口,由编译程序自动完成其他细节。在定义注解时,不能继承其他的注解或接口。@interface用来声明一个注解,其中的每一个方法实际上是声明了一个配置参数。方法的名称就是参数的名称,返回值类型就是参数的类型(返回值类型只能是基本类型、Class、String、enum)。可以通过default来声明参数的默认值。以上所有例子都属于自定义注解。自定义注解具有以下固定格式:

    public @interface 注解名{注解体} 1 1 所有基本数据类型(int,float,boolean,byte,double,char,long,short)String类型Class类型enum类型Annotation类型以上所有类型的数组

    注意:只能有public或默认(default)这两个访问权修饰,参数成员只能用以上6种类型,如果只有一个参数成员,最好把参数名称设为”value”。

    Shade 形状注解:

    @Target(ElementType.FIELD) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface Shade { public enum ShadeType { Triangle("三角"), Four("四边"), Five("五角"); private String type; ShadeType(String type) { this.type = type; } @Override public String toString() { return type; } } public ShadeType shader() default ShadeType.Triangle; } 12345678910111213141516171819202122232425 12345678910111213141516171819202122232425

    读取注解

    这里我们将使用反射去读取注解。Java在java.lang.reflect 包下新增了AnnotatedElement接口,该接口代表程序中可以接受注解的程序元素。该接口主要有如下几个方法:

    default boolean isAnnotationPresent(Class<? extends Annotation> annotationClass) 1 1

    判断该程序元素上是否包含指定类型的注解,存在则返回true,否则返回false。

    <T extends Annotation> T getAnnotation(Class<T> var1); 1 1

    返回该程序元素上存在的、指定类型的注解,如果该类型注解不存在,则返回null。

    Annotation[] getAnnotations(); 1 1

    返回该程序元素上存在的所有注解。

    Annotation[] getDeclaredAnnotations(); 1 1

    返回直接存在于此元素上的所有注释。与此接口中的其他方法不同,该方法将忽略继承的注释。(如果没有注释直接存在于此元素上,则返回长度为零的一个数组。)该方法的调用者可以随意修改返回的数组;这不会对其他调用者返回的数组产生任何影响。

    public static void getInfo(Class<?> clazz) { // 获取该类所有声明的方法 Field[] fields = clazz.getDeclaredFields(); if (fields == null) return; for (Field field : fields) { if (field.isAnnotationPresent(Mode.class)) { Mode m0 = field.getAnnotation(Mode.class); System.out.println("****name=" + m0.value()); } if (field.isAnnotationPresent(Shade.class)) { Shade s0 = field.getAnnotation(Shade.class); System.out.println("****shade=" + s0.shader().toString()); } if (field.isAnnotationPresent(People.class)) { People p0 = field.getAnnotation(People.class); System.out.println("****name=" + p0.Name() + "**age=" + p0.Age() + "**price=" + p0.Price()); } } } 12345678910111213141516171819202122232425 12345678910111213141516171819202122232425

    使用自定义注解:

    public class User { @Mode(value = "小石头") public String name; @Shade(shader = Shade.ShadeType.Five) public String shape; @People(Age = 18, Price = 100f, Name = "小宝") public int profile; } 123456789101112 123456789101112

    运行:

    getInfo(User.class); 1 1

    打印:

    ****name=小石头 ****shade=五角 ****name=小宝**age=18**price=100.0 123 123

    注意:使用反射去读取注解,必须将Retention的值选为Runtime。

    转载请注明原文地址: https://ju.6miu.com/read-663340.html

    最新回复(0)