注解可以被标注在任意地方,包括方法上、类名上、参数上、成员属性上、注解定义上等,就像注释一样,它相当于我们对某样东西的一个标记。而与注释不同的是,注解可以通过反射在运行时获取,注解也可以选择是否保留到运行时
预设注解
JDK预设了以下注解,作用于代码:
@Override
- 检查(仅仅是检查,不保留到运行时)该方法是否是重写方法。如果发现其父类,或者是引用的接口中并没有该方法时,会报编译错误。@Deprecated
- 标记过时方法。如果使用该方法,会报编译警告。@SuppressWarnings
- 指示编译器去忽略注解中声明的警告(仅仅编译器阶段,不保留到运行时)@FunctionalInterface
- Java 8 开始支持,标识一个匿名函数或函数式接口。@SafeVarargs
- Java 7 开始支持,忽略任何使用参数为泛型变量的方法或构造函数调用产生的警告。
元注解
元注解是一种最基本的注解,应用到其他的注解上面
元注解有 @Retention、@Documented、@Target、@Inherited、@Repeatable 5种
@Retention
Retention 的英文意为保留期的意思,当 @Retention 应用到一个注解上的时候,它解释说明了这个注解的存活时间。
它的取值如下:
- RetentionPolicy.SOURCE 注解只在源码阶段保留,在编译器进行编译时它将被丢弃忽视。
- RetentionPolicy.CLASS 注解只被保留到编译进行的时候,它并不会被加载到 JVM 中。
- RetentionPolicy.RUNTIME 注解可以保留到程序运行的时候,它会被加载进入到 JVM 中,所以在程序运行时可以获取到它们。
@Documented
能够将注解中的元素包含到 Javadoc 中去
Target 是目标的意思,@Target 指定了注解运用的地方。
你可以这样理解,当一个注解被 @Target 注解时,这个注解就被限定了运用的场景。
@Target 有下面的取值
- ElementType.ANNOTATION_TYPE 可以给一个注解进行注解
- ElementType.CONSTRUCTOR 可以给构造方法进行注解
- ElementType.FIELD 可以给属性进行注解
- ElementType.LOCAL_VARIABLE 可以给局部变量进行注解
- ElementType.METHOD 可以给方法进行注解
- ElementType.PACKAGE 可以给一个包进行注解
- ElementType.PARAMETER 可以给一个方法内的参数进行注解
- ElementType.TYPE 可以给一个类型进行注解,比如类、接口、枚举
@Inherited
Inherited 是继承的意思,但是它并不是说注解本身可以继承,而是说如果一个超类被 @Inherited 注解过的注解进行注解的话,那么如果它的子类没有被任何注解应用的话,那么这个子类就继承了超类的注解。
说的比较抽象,代码来解释。
@Inherited
@Retention(RetentionPolicy.RUNTIME)
@interface Test {}
@Test
public class A {}
public class B extends A {}
注解 Test 被 @Inherited 修饰,之后类 A 被 Test 注解,类 B 继承 A,类 B 也拥有 Test 这个注解。
可以这样理解:
老子非常有钱,所以人们给他贴了一张标签叫做富豪。
老子的儿子长大后,只要没有和老子断绝父子关系,虽然别人没有给他贴标签,但是他自然也是富豪。
老子的孙子长大了,自然也是富豪。
这就是人们口中戏称的富一代,富二代,富三代。虽然叫法不同,好像好多个标签,但其实事情的本质也就是他们有一张共同的标签,也就是老子身上的那张富豪的标签。
@Repeatable
Repeatable 自然是可重复的意思。@Repeatable 是 Java 1.8 才加进来的,所以算是一个新的特性。
什么样的注解会多次应用呢?通常是注解的值可以同时取多个。
举个例子,一个人他既是程序员又是产品经理,同时他还是个画家。
// 定义一个容器注解, 其value属性的数据类型是 Role 注解类型的数组
public @interface Person{
Role[] value();
}
// 定义 Role 注解, 使用@Repeatable注解标识 Role 注解与Person注解之间的关系
@Repeatable(Persons.class)
public @interface Role{
String role() default " ";
}
@Role(role="artist")
@Role(role="coder")
@Role(role="PM")
public class SuperMan{
}
// 两种方式等价,作用相同
@Person({@Role("artist"),@Role("coder"),@Role("PM")})
public class SuperMan{
}
@interface Persons {
Role[] value();
}
@Repeatable(Persons.class)
@interface Person{
String role default "";
}
@Person(role="artist")
@Person(role="coder")
@Person(role="PM")
public class SuperMan{
}
注解的使用
我们还可以在注解中定义一些属性,注解的属性也叫做成员变量,注解只有成员变量,没有方法。注解的成员变量在注解的定义中以“无形参的方法”形式来声明,其方法名定义了该成员变量的名字,其返回值定义了该成员变量的类型
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface Test {
String value();
}
默认只有一个属性时,我们可以将其名字设定为value,否则,我们需要在使用时手动指定注解的属性名称,使用value则无需填入:
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface Test {
String test();
}
public class Main {
@Test(test = "")
public static void main(String[] args) {
}
}
我们也可以使用default关键字来为这些属性指定默认值
当属性存在默认值时,使用注解的时候可以不用传入属性值
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface Test {
String value() default "都看到这里了,给个三连吧!";
}
当属性为数组,我们在使用注解传参时,如果数组里面只有一个内容,我们可以直接传入一个值,而不是创建一个数组
@Test("关注点了吗")
public static void main(String[] args) {
}
public class Main {
@Test({"value1", "value2"}) //多个值时就使用花括号括起来
public static void main(String[] args) {
}
}
反射获取注解
我们的注解可以保留到运行时,使用反射机制来获取我们编写的注解:
public static void main(String[] args) {
Class<Student> clazz = Student.class;
for (Annotation annotation : clazz.getAnnotations()) {
System.out.println(annotation.annotationType()); //获取类型
System.out.println(annotation instanceof Test); //直接判断是否为Test
Test test = (Test) annotation;
System.out.println(test.value()); //获取我们在注解中写入的内容
}
}