3.java注解

1,注解的概念

1.1 什么是注解

​ Annotation(注解)就是 Java 提供了一种元程序中的元素关联任何信息和任何元数据(metadata)的途径和方法。Annotation(注解)是一个接口,程序可以通过反射来获取指定程序元素的 Annotation 对象,然后通过 Annotation 对象来获取注解里面的元数据。

​ Annotation(注解)是 JDK1.5 及以后版本引入的。它可以用于创建文档,跟踪代码中的依赖性,甚至执行基本编译时检查。Annotation 就像修饰符一样被使用,并应用于包,类型,构造方法,方法,成员变量,参数,本地变量的声明中。

1.2 什么是元数据

​ 元数据就是“关于数据的数据”。元数据的功能有很多,比如:Javadoc的注释自动生成文档。元数据可以用来创建文档,跟踪代码的依赖性,执行编译时格式检查,代替已有的配置文件。

​ 根据元数据的作用可以分为以下几类:

​ 1,编写文档:通过代码里标识的元数据生成文档【生成文档doc文档】

​ 2,代码分析:通过代码里标识的元数据对代码进行分析【使用反射】

​ 3,编译检查:通过代码里标识的元数据让编译器能够实现基本的编译检查【Override】

​ 元数据标签的存在并不影响程序代码的编译和执行,它只是生成其它的外键或针对在运行代码的描述信息。综上所述:

​ 1,元数据以标签的形式存在于 Java 代码中

​ 2,元数据描述的信息是类型安全的,即元数据内部的字段都是有明确类型的

​ 3,元数据需要编译器之外的工具额外的处理用来生成其它的程序部件

​ 4,元数据可以只存在于 Java 源代码级别,也可以存在于编译之后的Class文件内容

2,注解的分类

2.1 根据注解的参数个数
1. **标记注解**:一个没有成员定义的 Annotation 类型称为标记注解。这种 Annotation 类型仅使用自身的存在与否来为我们提供信息
2. **单值注解**:在标记注解的基础上提供一段数据
3. **完整注解**:可以包括多个数据成员,每个数据成员由名称和值构成
2.2 根据注解使用方法和用途
1. **JDK内置标准注解**:注解的语法比较简单,除了@符号的使用外,基本与Java固有的语法一致,JavaSE中内置三个标准注解,定义在`java.lang`中
2. **元注解**:元注解的作用就是负责注解其他注解。Java5.0定义了4个标准的meta-annotation类型,它们被用来提供对其它 annotation 类型作说明
3. **自定义注解**:使用@interface 的语法来说明一个注解
3,系统内置标准注解

JavaSE中内置3个标准注解,定义在java.lang中:

  • @Override:用于修饰此方法覆盖了父类的方法
  • @Deprecated:用于修饰已经过时的方法
  • @SuppressWarnnings:用于通知java编译器禁止特定的编译警告
3.1 @Override

​ 表示限定重写父类方法

​ @Override 是一个标记注解类型,它被用作标记方法。说明了被标注的方法重载了父类的方法,起到了断言的作用

3.2 @Deprecated

​ 表示标记已过时

​ @Deprecated 是一个标记注解。

3.3 @SuppressWarnnings

​ 表示抑制编译器警告

SuppressWarnnings 注解的常见参数值得简单说明:

  1. deprecation:使用了不赞成使用的类或方法时的警告

    1. unchecked:执行了为检查的转换时的警告,例如当使用集合时没有用泛型(Generics)来指定集合保存的类型
    2. fallthrough:当switch程序块直接通往下一种情况而没有 Break 时的警告
    3. path:在类路径,源文件等中有不存在的路径时的警告
    4. serial:当在可序列化的类上缺少 定义时的警告
    5. finally:任何 finally 子句不能正常完成时的警告
    6. all:关于以上所有情况的警告
3.4 @FunctionalInterface

JDK8出现,主要⽤来指示⼀个接⼝是⼀个函数接⼝,以提示编译器编译时检查接⼝是否符合函数接⼝的规范.

4,元注解

​ 元注解的作用就是负责注解其他注解。JavaEE5 定义了4个标准的 meta-annotation 类型,它们被用来提供对其它 annotation 类型作说明。JavaEE5定义的元注解:

  • @Target

  • @Retention

  • @Documented

  • @Inherited

    这些类型和它们所支持的类在 java.lang.annotation 包中可以找到。

4.1 @Target

​ @Target 说明了 Annotation 所修饰的对象范围:Annotation可被用于packages,types(类,接口,枚举,Annotation类型),类型成员(方法,构造方法,成员变量,枚举值),方法参数和本地变量(如循环变量,catch参数)

作用:用于描述注解的使用范围(即:被描述的注解可以用在什么地方)

取值ElementType)有:

​ 1.CONSTRUCTOR:用于描述构造器

​ 2.FIELD:用于描述域

​ 3.LOCAL_VARIABLE:用于描述局部变量

​ 4.METHOD:用于描述方法

​ 5.PACKAGE:用于描述包

​ 6.PARAMTER:用于描述参数

​ 7.TYPE:用于描述类,接口(包括注解类型)或enum声明

@Target(ElementType.TYPE)
public @interface MyAnno {
    String value() default "abc";
}
@Target(ElementType.FIELD)
public @interface MyAnno2 {
    
}
4.2 @Retention

​ @Retention 定义了该 Annotation 被保留的时间长短

作用:表示需要在什么级别保存该注解信息,用于描述注解的生命周期(即:被描述的注解在什么范围内有效)

取值RetentionPoicy)有:

  • SOURCE:在源文件中有效(即源文件保留)

  • CLASS:在class文件中有效(即class保留)

  • RUNTIME:在运行时有效(即运行时保留)

    Retention 元注解类型有唯一的value作为成员,它的取值来自 java.lang.annotation.RetentionPolicy的枚举类型值

    @Target(ElementType.TYPE)
    @Retention(RetentionPolicy.RUNTIME)
    public @interface MyAnno {
        String value();
        String m1();
        int m2() default 1;
    }
    
4.3 @Documented

​ @Documented 用于描述其它的 annotation 类型应该被作为被标注的程序成员的公共API,因此可以被例如 javadoc 此类的工具文档化。Documented 是一个标记注解,没有成员

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface MyAnno {
    String value();
    String m1();
    int m2() default 1;
}
4.4 @Inherited

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

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

@Inherited
public @interface MyAnno {
    String value();
    String m1();
    int m2() default 1;
}

​ 作用在类上的自定义注解可以被继承下来。作用在接口上自定义注解不能被实现它的类继承下来。类和接口中方法上的自定义注解不能被重写/实现了其它方法的子类继承(就是说,接口因为被子类实现的时候,方法必被实现,所以其方法上的自定义注解必定不能被继承。而子类如果没有重写父类的方法的话,子类就可以继承父类方法上的自定义注解)

4.5 @Repeatable

​ 此注解是JDK8引⼊的,⽤以表明注解在修饰某⼀个程序单元时,被@Repeatable修饰的注解可以多次出现
在成员单元上.⽐如下⾯的@SomeAnno注解就多次修饰了SomeClass这个程序单元.

5,自定义注解

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

5.1 定义注解格式

public @interface 注解名 {定义体}

5.2 注解参数的可支持数据类型

​ 1,所有基本数据类型(int,float,Boolean,byte,double,char,long,short)

​ 2,String类型

​ 3,Class类型

​ 4,enum类型

​ 5,Annotation类型

​ 6,以上所有类型的数组

5.3 Annotation 类型里面的参数改怎么设定

​ 1,只能用 public 或默认(default)这两个访问修饰符

​ 2,参数成员只能用基本数据类型,byte,short,char,int,long,float,double,boolean 8种基本数据类型和String,Enum,Class,annotation等数据类型,以及这一些类型的数组

​ 3,如果只有一个参数成员,最好把参数名设为“value”,后加小括号,

5.4 注解元素的默认值

​ 注解元素必须有确定的值,要么在定义注解的默认值中指定,要么在使用注解时指定,非基本数据类型的注解元素的值不可为null。因此,使用空字符串或0作为默认值是一种常用的做法。这个约束使得处理器很难表现一个元素的存在或缺失的状态,因为每个注解的声明中,所有元素都存在,并且都具有相应的值,为了绕开这个约束,我们只能定义一些特殊的值,例如空字符串或者负数,以表示某个元素不存在。

5.5 解析注解

​ 使⽤注解修饰了程序单元之后,如果没有对其进⾏解析,这些注解不会对程序的运⾏有任何的影响.必须对这些注解进⾏解析让其真正的发挥作⽤.注解的解析其核⼼思路是

  • 通过反射找出被注解修饰的程序单元的反射类型表示,⽐如Class,Method,Field
  • 依据这些被修饰单元的反射类型表示,通过相关的⽅法获取注解信息.
5.5.1 AnnotatedElement

​ 在JDK中⽤⼀个接⼝类型 AnnotatedElement 来代表被注解修饰的程序单元,class对象,Method对象,Field对象等都实现了此接⼝.此接⼝的作⽤就是⽤来获取被注解修饰的程序单元上的注解.此接⼝提供了如下成员

  • getAnnotation(Class annotationClass):返回修饰改程序单元的指定类型的注解,不存在则返回null
  • getDeclaredAnnotation(Class annotationClass):返回直接修饰程序单元的指定类型的注解,不包含继承过来你的注解,不存在则返回null
  • getDeclaredAnnotations:返回直接修饰在单元上的所有注解
  • isAnnotationPresent(Class annotationClass):指定的注解是否修饰在了程序单元上,是就返回true,否则返回false.
  • getAnnotationsByType(Class annotationClass):返回修饰程序单元的指定类型的多个注解,返回类型是个数组,不存在则返回null,这是jdk8新加的,因为jdk8推出了可重复(Repeatable)注解.
  • getDeclaredAnnotationsByType(Class annotationClass):返回直接修饰该程序单元的指定类型的多个注解,不存在则返回 null。

⽐如下⾯的代码就是找出修饰在类上的所有注解.

Class<UserInfoEntity> clazz = UserInfoEntity.class;
Annotation[] annos = clazz.getDeclaredAnnotations();

所有的注解都是 java.lang.annotation.Annotation 类型的⼦类.所以上⾯的getDeclaredAnnotations⽅法的返回值⽤Annotation数组接收.

如果要得到某个⽅法上⾯的所有注解,可以⽤下⾯的代码

Method m = ...;
Annotation[] methodAnnos = m.getDeclaredAnnotations();

如果想得到某⼀个特定的注解,可以采⽤ getDeclaredAnnotation(class) ⽅法来实现,如果没有这
个特定的注解,getDeclaredAnnotation⽅法就返回null.⽐如下⾯的代码

Method m = null;
Table talbeAnno = m.getDeclaredAnnotation(Table.class);

下⾯的代码就获取到了UserInfoEntity类上修饰的Table注解的schema的值.

Class<UserInfoEntity> clazz = UserInfoEntity.class;
boolean haveTableAnno = clazz.isAnnotationPresent(Table.class);
if(haveTableAnno) {
 Table tableAnno = clazz.getDeclaredAnnotation(Table.class);
 System.out.println(tableAnno.schema());
}
6,可重复注解

​ 在JDK 8 之前,注解不⽀持重复修饰同⼀个程序单元,所以必须采取技巧性的代码来处理这种需求.其做法是先创建⼀个普通的注解,⽐如下⾯

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface MyAnno {
 String value() default "";
}

接着再创建⼀个容器注解,其某⼀个成员变量的类型是上⾯注解的数组类型,⽐如:

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface MyAnnoContainer {
 MyAnno[] value();
}

之后就把容器注解修饰在某个程序单元上,value成员变量就指定多个想重复出现的注解,⽐如

@MyAnnoContainer({@MyAnno("a"),@MyAnno("b")})
public class UserInfoEntity {
}

上⾯的实现⽅式语义不明确,因为⽬的是想多次出现MyAnno注解,但实际修饰在类上的却是另⼀个注解MyAnnoContainer.

在JDK8之后推出了⼀个@Repeatable元注解,此注解直接添加到MyAnno注解上,然后给@Repeatable元注解的value属性指定容器注解即可.最后直接把MyAnno注解修饰在程序单元上.最后的代码如下:

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Repeatable(value = MyAnnoContainer.class )
public @interface MyAnno {
 String value() default "";
}

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface MyAnnoContainer {
 MyAnno[] value();
}
@MyAnno("a")
@MyAnno("b")
public class UserInfoEntity {
}

在UserInfoEntity上修饰了多个@MyAnno之后就不能修饰容器注解MyAnnoContainer了.

想获取这个可重复的注解可以利⽤JDK先推出的⽅法getAnnotationsByType⽅法,如果找不到指定的注解就返回⼀个⻓度为0的数组.⽐如下⾯的代码

Class<UserInfoEntity> clazz = UserInfoEntity.class;
MyAnno[] myAnnos = clazz.getAnnotationsByType(MyAnno.class);
for(MyAnno anno:myAnnos) {
 System.out.println(anno.value());
}

这种可重复的注解利⽤getDeclaredAnnotation⽅法是取不到的.返回的是null

7,类型注解

在JDK 8 之前依据@Target设定注解可以修饰的地⽅,主要可以修饰的地⽅有类型,⽅法,字段,参数等.从JDK8开始,注解可以⽤在任何使⽤类型的地⽅,⽐如初始化(new)⼀个对象,使⽤implements表达式时.⽐如下⾯的代码

//实例化对象时
new @Interned MyObject();
//类型转换时
myString = (@NonNull String) str;
//实现语句时
class UnmodifiableList<T> implements
 @Readonly List<@Readonly T> { ... }
// 异常声明时.
void monitorTemperature() throws
 @Critical TemperatureException { ... }
// 泛型声明时
List<@NotNull String> list

定义⼀个这样的类型注解与定义普通的注解类似,只需要指定 Target 为
ElementType.TYPE_PARAMETER 或者 ElementType.TYPE_USE,或者同时指定这两个 Target。⽐如:

@Target({ElementType.TYPE_PARAMETER, ElementType.TYPE_USE})
public @interface MyAnnotation {
}

ElementType.TYPE_PARAMETER 表示这个 Annotation 可以⽤在 Type 的声明式前,⽽
ElementType.TYPE_USE 表示这个 Annotation 可以⽤在所有使⽤ Type 的地⽅(如:泛型,类型转换等)

Java 8 通过引⼊ Type Annotation,使得开发者可以在更多的地⽅使⽤ Annotation,从⽽能够更全⾯地对代码进⾏分析以及进⾏更强的类型检查.

想让这些类型其作⽤,还需要这个注解的处理器,以便在编译的时候利⽤这些检查的注解处理器解析这些注解.在https://checkerframework.org/⽹址有⼀个这样的注解解析器的使⽤说明.

8,注解处理器

​ 就是实现类似lombok功能的东⻄,请参考https://juejin.im/post/5a0ae00451882535cd4a5a29这篇⽂章

参考资料

https://docs.oracle.com/javase/tutorial/java/annotations

https://www.jianshu.com/p/28edf5352b63

https://www.ibm.com/developerworks/cn/java/j-lo-java8annotation/index.html

https://juejin.im/post/5a0ae00451882535cd4a5a29

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值