1.基本概念
从jdk1.5开始,java支持在源文件中嵌入一些补充信息,这类信息被称为 注解 (Annotation) 。注解不会改变程序的动作,因此也就不会改变程序的语义。但是在开发和部署期间,各种工具可以使用这类信息。
那么,注解和注释到底有什么区别呢? 从源代码的层面看,两者基本没有什么区别。但是,注解是可以随源码编译到字节码文件中,在运行时依然可以存在,注释则不可以,这也是二者最大的不同。
java中的术语 “元数据”也是用于表示这个特性,与注解是同一语义。
2.注解的基础知识
先来看一段声明注解的代码代码:
@interface MyAnnotation{ String str(); int val(); }
有没有发现有点和java的接口类似!但是他和接口还是有一点区别的:
区别1:interface前必须有@符号来表示这是一个注解,并且所有的注解自动拓展了java.lang.Annotation接口。
区别2:注解中的所有成员都只能包含方法声明,但是不能为这些方法提供方法体(使用时直接通过名称赋值,有点像属性)。
那么,我们声明完了注解以后该如何使用呢?很简单,只需要在使用的地方进行标注就可以了 ,见下面的代码
@MyAnnotation(str ="xiaobai", val = 5) public class MyClass{ }
这就是注解的使用方法,通过这样的标注,MyClass就绑定上了@MyAnnotation 注解,若该注解保留到运行时,则我们可以通过反射的方式读取到这个注解(至于什么是反射,我后面的文章会慢慢提到)
3.注解的保留策略
前面我们说到,注解和注释的一大区别就是注解可以保留到字节码甚至运行时环境中,而注释只能存在在源码级别,下面我们就来看一下注解是怎么保留到字节码甚至更高级别的。
注解的保留策略决定了在什么位置丢弃注解,java中定义了三种策略,这三种策略被封装到java.lang.Annotation.RetentionPolicy枚举中。这三种策略分别是:SOURCE、CLASS、RUNTIME
使用 SOURCE 保留策略的注解:只在源文件中保留,在编译期间会被抛弃。
使用CLASS 保留策略的注解:在编译时被存储到.class文件中,但是在运行时通过JVM不能得到这些注解。
使用RUNTIME保留策略的注解:在编译时被存储到.class文件中,并且在运行时可以通过JVM获取这些注解。因此RUNTIME保留策略提供了最永久的注解保留策略
在注解上还可以继续打注解,注解的保留策略就是通过注解@Retention 指定的
@Retention(RUNTIME) //指定保留到运行时 @interface MyAnnotation{ String str(); int val(); }
4. 注解的默认值
上面的示例中注解有两个值 str 和 val 。 我们在使用注解的时候需要给这两个值进行赋值,但是有的时候可能我们并不想给他们赋值,这就需要注解有默认值。
注解默认值的声明方式很简单,就是在注解中的方法后面加上一个 defaule + 值的形式就可以了 。
@interface MyAnnotation{ String str() default "xiaobai"; int val() default 100; }
这样注解就有默认值了。如果我们不指定值,则使用默认值;如果我们指定了值,则使用我们指定的值。
5.标记注解和单成员注解
标记注解就是不存在成员的注解,其目的就是为了标记,方便后期的反射等功能读取。(标记注解不需要使用圆括号,因为没有成员需要赋值)
单成员注解:顾名思义,就是只有一个成员的注解,若成员名是 value 的时候,可以省略成员名,直接进行赋值
@interface MyAnnotation{ String value(); } @MyAnnotation("xiaobai") class MyClass{ }
6.内置注解
java为我们提供了一些内置注解供我们使用:
4个来自java.lang.annotation 包:@Retention 、 @Documented 、@Target 和 @Inherited
5个来自java.lang 包:@Override 、 @Deprecated 、@FunctionalInterface 、@SafeVarargs 和 @SupressWarnings
下面我们来逐个介绍一下:
6.1 @Retention
该注解被设计为只能作用于其他注解(即只能标注在注解上),用来指定注解的保留策略
6.2@Documented
该注解被设计为只能作用于其他注解(即只能标注在注解上),用于通知某个工具注解将被文档化。
6.3@Target
该注解被设计为只能作用于其他注解(即只能标注在注解上),用于指定可以被该注解标注的类型(如 标注在类上、标注在方法上、标注在属性上等)
该注解只有一个参数,这个参数必须是来自ElementType的枚举常量,下面表格介绍了每个常量的意义:
eg:如果指定某个注解只能作用于域变量和局部变量 则可以使用
6.4@Inherited
该注解被设计为只能作用于其他注解(即只能标注在注解上),并且只影响用于类声明的注解。它会导致超类的注解被子类继承。
当查询子类注解时,若子类注解不存在,则检查其父类注解,如果被查询的注解在父类中存在并且被@Inherited标注,则会返回这个注解。
6.5 @Override
这个注解相信大家都不陌生,该注解只能作用于方法,被该注解标注的方法必须要重写父类方法,否则编译报错。
6.6@Deprecated
这个注解用于标记过时(不再使用)并且已经被更新的形式取代,可以作用于类、属性、方法上。
6.7@FunctionalInterface
该注解是jdk1.8新增的注解,用于标记接口,指出被注解的接口是一个函数式接口,函数式接口是指仅包含一个抽象方法的接口,由lambda表达式使用。
6.8@SafeVarargs
该注解只能用于方法和构造函数,用来指示没有发生与可变长参数有关的不安全动作。
6.9@SupressWarnings
该注解用于抑制编译器的警告,使用字符串隐式指定需要抑制的警告
7.类型注解
Java8 为 ElementType 枚举增加了TYPE_PARAMETER、TYPE_USE两个枚举值,从而可以使用 @Target(ElementType_TYPE_USE) 修饰注解定义,这种注解被称为类型注解,可以用在任何使用到类型的地方。
在 java8 以前,注解只能用在各种程序元素(定义类、定义接口、定义方法、定义成员变量…)上。从 java8 开始,类型注解可以用在任何使用到类型的地方。
TYPE_PARAMETER:表示该注解能写在类型参数的声明语句中。 类型参数声明如: <T>、<T extends Person>
TYPE_USE:表示注解可以再任何用到类型的地方使用,比如允许在如下位置使用:
1.创建对象(用 new 关键字创建)
2.类型转换
3.使用 implements 实现接口
4.使用 throws 声明抛出异常
1 @Target(ElementType.TYPE_USE) 2 @interface NotNull{ 3 4 } 5 6 // 定义类时使用 7 // 在implements时使用 8 @NotNull 9 public class TypeAnnotationTest implements Serializable { 10 11 // 在方法形参中使用 12 // 在throws时使用 13 public static void main(@NotNull String [] args) throws @NotNull FileNotFoundException { 14 15 Object obj = "fkjava.org"; 16 17 // 使用强制类型转换时使用 18 String str = (@NotNull String) obj; 19 20 // 创建对象时使用 21 Object win = new (@NotNull) JFrame("疯狂软件"); 22 } 23 24 // 泛型中使用 25 public void foo(List<@NotNull String> info) { 26 27 } 28 }
这种无处不在的注解,可以让编译器执行更严格的代码检查,从而提高程序的健壮性。
需要指出的是,上面程序虽然使用了大量 @NotNull 注解,但是这些注解暂时不会起任何作用——因为没有为这些注解提供处理工具,
java8本身并没有提供,要想这些注解发挥作用,需要开发者自己实现,或者使用第三方提供的工具。
8.重复注解
重复注解:即允许在同一申明类型(类,属性,或方法)前多次使用同一个类型注解。
在java8 以前,同一个程序元素前最多只能有一个相同类型的注解;如果需要在同一个元素前使用多个相同类型的注解,则必须使用注解“容器”
早期的使用方式:
public @interface Authority { String role(); } public @interface Authorities { //@Authorities注解作为可以存储多个@Authority注解的容器 Authority[] value(); } public class RepeatAnnotationUseOldVersion { @Authorities({@Authority(role="Admin"), @Authority(role="Manager")}) public void doSomeThing(){ } }
java8 提供了重复注解后的使用方式:
@Repeatable(Authorities.class) public @interface Authority { String role(); } public @interface Authorities { Authority[] value(); } public class RepeatAnnotationUseNewVersion { @Authority(role="Admin") @Authority(role="Manager") public void doSomeThing(){ } }
不同的地方是,创建重复注解 Authority 时,加上@Repeatable,指向存储注解 Authorities,在使用时候,直接可以重复使用 Authority 注解。从上面例子看出,java 8里面做法更适合常规的思维,可读性强一点。但是,仍然需要定义容器注解。
两种方法获得的效果相同。重复注解只是一种简化写法,这种简化写法是一种假象:多个重复注解其实会被作为“容器”注解的 value 成员的数组元素处理。
9.一些限制
使用注解声明有许多限制。首先一个注解不能继承另一个注解。其次注解声明的所有方法都必须不带参数,此外,他们不能返回一下类型的值:
1.String 或 Class类型的对象 2.枚举类型 3.其他注解类型 4.上述类型的数组
同时,注解也不能被泛型化,注解方法也不能指定throws语句。