Java元注解
注解的注解,称为元注解。
@Target
作用:用于描述注解的使用范围(即:被描述的注解可以用在什么地方)。
所修饰的对象范围:
ElementType:
TYPE
:类、接口(包括注解类型)和枚举的声明FIELD
:字段声明(包括枚举常量)METHOD
:方法声明PARAMETER
:参数声明CONSTRUCTOR
:构造函数声明LOCAL_VARIABLE
:本地变量声明ANNOTATION_TYPE
:注解类型声明PACKAGE
:包声明TYPE_PARAMETER
:类型参数声明,JavaSE 8
引进,可以应用于类的泛型声明之处TYPE_USE
:能标注任何类型名称JavaSE 8
引进,此类型包括类型声明和类型参数声明,是为了方便设计者进行类型检查
在Annotation
类型的声明中使用了target
可更加明晰其修饰的目标。
注意:如果一个注解没有指定@Target
注解,则此注解可以用于除了TYPE_PARAMETER
和TYPE_USE
以外的任何地方。
/**
* <p> 描述 : 使用TYPE_PARAMETER类型,该注解只能在类型参数声明时使用
* @author : blackcat
* @date : 2020/8/6 15:04
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE_PARAMETER)
public @interface TypeParammeterAnnotation {
}
/**
* <p> 描述 : 使用TYPE_USE类型,该注解能标注任何类型名称
* @author : blackcat
* @date : 2020/8/6 15:38
*/
@Target(ElementType.TYPE_USE)
@Retention(RetentionPolicy.RUNTIME)
public @interface TypeUseAnnotation {
}
/**
* <p> 描述 : 泛型类型声明时,使用使用TYPE_PARAMETER类型,编译通过
* @author : blackcat
* @date : 2020/8/6 15:05
*/
public class TestTypeParameter<@TypeParammeterAnnotation T> {
/** 使用TYPE_PARAMETER类型,会编译不通过 */
/*public @TypeParammeterAnnotation T test(@TypeParammeterAnnotation T a){
new ArrayList<@TypeParammeterAnnotation String>();
return a;
}*/
}
/**
* <p> 描述 : ElementType.TYPE_USE使用示例
* 使用TYPE_USE类型,该注解能标注任何类型名称
* @author : blackcat
* @date : 2020/8/6 15:33
*/
public class TestTypeUse {
/** 泛型类型声明时,使用TYPE_USE类型,编译通过 */
public static @TypeUseAnnotation class TypeUseClass<@TypeUseAnnotation T> extends @TypeUseAnnotation Object {
/** 使用TYPE_USE类型,编译通过 */
public void foo(@TypeUseAnnotation T t) throws @TypeUseAnnotation Exception {
}
}
// 如下注解的使用都是合法的
@SuppressWarnings({ "rawtypes", "unused", "resource" })
public static void main(String[] args) throws Exception {
TypeUseClass<@TypeUseAnnotation String> typeUseClass = new @TypeUseAnnotation TypeUseClass<>();
typeUseClass.foo("");
List<@TypeUseAnnotation Comparable> list1 = new ArrayList<>();
List<? extends Comparable> list2 = new ArrayList<>();
@TypeUseAnnotation String text = (@TypeUseAnnotation String)new Object();
java.util. @TypeUseAnnotation Scanner console = new java.util.@TypeUseAnnotation Scanner(System.in);
}
}
/**
* <p> 描述 : ElementType.METHOD只能修饰方法。
* @author : blackcat
* @date : 2020/8/6 16:37
*/
@Target(ElementType.METHOD)
public @interface MethodAnnotation {
}
/**
* <p> 描述 : 测试
* @author : blackcat
* @date : 2020/8/6 16:38
*/
public class TestMethodAnnotation {
/** 编译不通过ElementType.METHOD 不能修饰成员变量,只能修饰方法。 */
/*@MethodAnnotation
String name;*/
@MethodAnnotation
public void test(){}
}
对于其他的ElementType
注解元素,看ElementType
类的注释即可知道如何使用。
@Retention
这种类型的注解会被保留到那个阶段. 有三个值:
RetentionPolicy.SOURCE
—— 注解只保留在源文件,当Java
文件编译成class
文件的时候,注解被遗弃;RetentionPolicy.CLASS
—— 注解被保留到class
文件,但Jvm
加载class
文件时候被遗弃,这是默认的生命周期;RetentionPolicy.RUNTIME
—— 注解不仅被保存到class
文件中,Jvm
加载class
文件之后,仍然存在;
这3个生命周期分别对应于:Java
源文件(.java
文件) ---> .class
文件 ---> 内存中的字节码。
那怎么来选择合适的注解生命周期呢?
首先要明确生命周期长度 SOURCE
< CLASS
< RUNTIME
,所以前者能作用的地方后者一定也能作用。
- 一般如果需要在运行时去动态获取注解信息,那只能用
RUNTIME
注解,比如@Deprecated
使用RUNTIME
注解; - 如果要在编译时进行一些预处理操作,比如生成一些辅助代码(如
ButterKnife
),就用CLASS
注解; - 如果只是做一些检查性的操作,比如
@Override
和@SuppressWarnings
,使用SOURCE
注解。
注解@Override
用在方法上,当我们想重写一个方法时,在方法上加@Override
,当我们方法的名字出错时,编译器就会报错。
注解@Deprecated
,用来表示某个类或属性或方法已经过时,不想别人再用时,在属性和方法上用@Deprecated
修饰。
注解@SuppressWarnings
用来压制程序中出来的警告,比如在没有用泛型或是方法已经过时的时候。
/**
* <p> 描述 : 示不仅会在编译后的class文件中存在,而且在运行时保留。
* 因此它们主要用于反射场景,可以通过getAnnotation方法获取。
* @author : blackcat
* @date : 2020/8/6 16:08
*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface Country {
/** 国家名称 */
String name();
/** 国家语言 */
String[] languages();
}
/**
* <p> 描述 : 默认策略,表示注解会在编译后的class文件中存在,但是在运行时,不会被VM保留。
* @author : blackcat
* @date : 2020/8/6 16:09
*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.CLASS)
public @interface Region {
/** 地区名称 */
String name();
/** 所属国家 */
String country();
}
/**
* <p> 描述 : 表示注解会在编译时被丢弃
* @author : blackcat
* @date : 2020/8/6 16:10
*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.SOURCE)
public @interface Home {
/** 成员 */
String[] members();
/** 地址 */
String address();
}
/**
* <p> 描述 : 测试
* @author : blackcat
* @date : 2020/8/6 16:14
*/
@Country(
name = "China",
languages = {"Chinese"}
)
@Region(
name = "GuangDong",
country = "China"
)
@Home(
members = {"Wolffy","Wolnie","Wilie"},
address = "Qingqing grasslands"
)
public class TestAnnotation {
public static void main(String[] args) {
Annotation[] annotations = TestAnnotation.class.getAnnotations();
System.out.println("获取能保留到运行时的注解:");
for (Annotation annotation :annotations){
System.out.println(annotation.toString());
}
/*
print:
获取能保留到运行时的注解:
@com.blackcat.metaannotation.retention.Country(name=China, languages=[Chinese])
*/
}
}
@Documented
是一个标记注解,没有成员变量。用
@Documented
注解修饰的注解类会被JavaDoc
工具提取成文档。
默认情况下,JavaDoc
是不包括注解的,但如果声明注解时指定了 @Documented
,就会被 JavaDoc
之类的工具处理,所以注解类型信息就会被包括在生成的帮助文档中。
/**
* <p> 描述 : 用 @Documented 注解修饰的注解类会被 JavaDoc 工具提取成文档
* @author : blackcat
* @date : 2020/8/6 16:47
*/
@Documented
@Target({ ElementType.TYPE, ElementType.METHOD })
public @interface MyDocumented {
String value() default "这是@Documented注解";
}
/**
* <p> 描述 :测试document
* @author : blackcat
* @date : 2020/8/6 16:48
*/
@MyDocumented
public class TestDocumented {
/**
* 测试document
*/
@MyDocumented
public String Test() {
return "测试document";
}
}
打开 Java
文件所在的目录,分别输入如下两条命令行:
javac -encoding utf-8 MyDocumented.java TestDocumented.java
javadoc -encoding utf-8 -d doc MyDocumented.java TestDocumented.java
在当前目录下生成的doc
文件夹里的index.html
可以看到以下内容。
@Inherited
是一个标记注解,用来指定该注解可以被继承。
使用 @Inherited
注解的 Class
类,表示这个注解可以被用于该 Class
类的子类。就是说如果某个类使用了被 @Inherited
修饰的注解,则其子类将自动具有该注解。
注意:此注解只对注解标记的超类有效,对接口是无效的。
/**
* <p> 描述 : 简称 用@Inherited标记的注解
* 注意:此注解只对注解标记的超类(被继承的类-父类)有效,对接口是无效的。
* @author : blackcat
* @date : 2020/8/7 9:10
*/
@Inherited
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface Abbreviation {
/** 简称 */
String value();
}
/**
* <p> 描述 : 名称 用@Inherited标记的注解
* 注意:此注解只对注解标记的超类(被继承的类-父类)有效,对接口是无效的。
* @author : blackcat
* @date : 2020/8/7 9:10
*/
@Inherited
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface Name {
/** 名称 */
String value();
}
/**
* <p> 描述 : 大写 没有用@Inherited标记的注解
* @author : blackcat
* @date : 2020/8/7 9:11
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface UpperCaseName {
/** 大写英文名称 */
String value();
}
声明一些接口和类用于举例:
/**
* <p> 描述 : 有机体
* @author : blackcat
* @date : 2020/8/7 9:13
*/
@UpperCaseName("ORGANISM")
@Abbreviation("Ogm")
@Name("Organism")
public interface Organism {
}
/**
* <p> 描述 : 植物
* @author : blackcat
* @date : 2020/8/7 9:14
*
* Inherited只作用于子类与父类之间的继承
* Plant接口继承Organism接口,在Organism接口上标记的注解,Plant获取不到
*/
public interface Plant extends Organism{
}
/**
* <p> 描述 : 花
* @author : blackcat
* @date : 2020/8/7 9:14
*/
@UpperCaseName("FLOWER")
@Abbreviation("Flr")
@Name("Flower")
public abstract class Flower implements Plant {
}
/**
* <p> 描述 : 玫瑰
* @author : blackcat
* @date : 2020/8/7 9:15
*
* Rose类继承Flower抽象类,在Flower抽象类上标记的注解,如果注解是被@Inherited标记的,都可以获取到
*/
public class Rose extends Flower {
}
/**
* <p> 描述 : 动物
* @author : blackcat
* @date : 2020/8/7 9:15
*/
@UpperCaseName("ANIMAL")
@Abbreviation("Ani")
@Name("Animal")
public interface Animal extends Organism{
}
/**
* <p> 描述 : 哺乳动物
* @author : blackcat
* @date : 2020/8/7 9:15
*
* Mamanl抽象类实现Animal接口,在Animal接口上标记的注解,Mammal获取不到
*/
public abstract class Mammal implements Animal {
}
/**
* <p> 描述 : 猴子
* @author : blackcat
* @date : 2020/8/7 9:16
*/
@UpperCaseName("MONKEY")
@Abbreviation("Mky")
@Name("Monkey")
public class Monkey extends Mammal{
}
/**
* <p> 描述 : 金丝猴
* @author : blackcat
* @date : 2020/8/7 9:16
*
* Roxellanae类继承Monkey类,在Monkey抽象类上标记的注解,如果注解是被@Inherited标记的,都可以获取到;
* 如果被@Inherited标记的注解父类和子类重复标记,则返回子类的注解
*/
@Name("Roxellanae")
public class Roxellanae extends Monkey {
}
测试:
package com.blackcat.metaannotation.inherited;
import java.lang.annotation.Annotation;
/**
* <p> 描述 : 测试
* @author : blackcat
* @date : 2020/8/7 9:17
*
* 说明:
* 01 Abbreviation 注解标记 @Inherited
* 02 Name 注解标记 @Inherited
* 03 UpperCaseName 注解 没有标记 @Inherited
*
* Organism(接口 01,02,03) <- Plant(接口) <- Flower(抽象类 01,02,03) <- Rose(类)
* Organism(接口 01,02,03) <- Animal(接口) <- Mammal(抽象类) <- Monkey(类 01,02,03) <- Roxellanae(类 02)
*
* 结论:
* 没有被@Inherited注解标记的注解,如:@UpperCaseName注解,就不具有继承特性,在子类中获取不到父类的注解。
*
* Inherited注解继承概念跟我们理解的面向对象继承概念不一样,它只作用于子类与父类之间的继承。
* 如:Rose类就从Flower父类中继承了@Abbreviation和@Name注解;对于接口之间的继承和类与接口之间的实现,这两种继承关系不起作用。
* 如:Plant接口继承Organism接口、Mamanl类实现Animal接口,就没能继承@Abbreviation和@Name注解。
*
* Inherited注解标记的注解,在使用时,如果父类和子类都使用的注解是同一个,那么子类的注解会覆盖父类的注解。
* 如:Roxellanae类用@Name注解标记了,Monkey类也用@Name注解标记了,那么Roxellanae类注解,会覆盖Monkey的@Name注解。
*/
public class TestInheritedAnnotation {
public static void main(String[] args){
Annotation[] annotations = Plant.class.getAnnotations();
System.out.print("Plant接口继承Organism接口,在Organism接口上标记的注解,Plant获取不到:");
for (Annotation annotation : annotations){
System.out.print(annotation.toString()+" ");
}
annotations = Mammal.class.getAnnotations();
System.out.print("
Mamanl抽象类实现Animal接口,在Animal接口上标记的注解,Mammal获取不到:");
for (Annotation annotation : annotations){
System.out.print(annotation.toString()+" ");
}
annotations = Rose.class.getAnnotations();
System.out.print("
Rose类继承Flower抽象类,在Flower抽象类上标记的注解,如果注解是被@Inherited标记的,都可以获取到:");
for (Annotation annotation : annotations){
System.out.print(annotation.toString()+" ");
}
annotations = Roxellanae.class.getAnnotations();
System.out.print("
Roxellanae类继承Monkey类,在Monkey抽象类上标记的注解,如果注解是被@Inherited标记的,都可以获取到;如果被@Inherited标记的注解父类和子类重复标记,则返回子类的注解:");
for (Annotation annotation : annotations){
System.out.print(annotation.toString()+" ");
}
}
}
输出结果:
Plant接口继承Organism接口,在Organism接口上标记的注解,Plant获取不到:
Mamanl抽象类实现Animal接口,在Animal接口上标记的注解,Mammal获取不到:
Rose类继承Flower抽象类,在Flower抽象类上标记的注解,如果注解是被@Inherited标记的,都可以获取到:@com.blackcat.metaannotation.inherited.annotation.Abbreviation(value=Flr) @com.blackcat.metaannotation.inherited.annotation.Name(value=Flower)
Roxellanae类继承Monkey类,在Monkey抽象类上标记的注解,如果注解是被@Inherited标记的,都可以获取到;如果被@Inherited标记的注解父类和子类重复标记,则返回子类的注解:@com.blackcat.metaannotation.inherited.annotation.Abbreviation(value=Mky) @com.blackcat.metaannotation.inherited.annotation.Name(value=Roxellanae)
@Repeatable
Java 8
新增加的,它允许在相同的程序元素中重复注解,在需要对同一种注解多次使用时,往往需要借助 @Repeatable
注解。
Java 8
版本以前,同一个程序元素前最多只能有一个相同类型的注解,如果需要在同一个元素前使用多个相同类型的注解,则必须使用注解@Component
。
/**
* <p> 描述 : 声明一个重复注解类
* @author : blackcat
* @date : 2020/8/7 10:38
*
* 创建重复注解 Schedule 时加上了 @Repeatable 注解,指向存储注解 Schedules,
* 这样在使用时就可以直接重复使用 Schedule 注解。
*/
@Repeatable(Schedules.class)
public @interface Schedule {
String dayOfMonth() default "1月";
String dayOfWeek() default "1周";
int hour() default 12;
}
/**
* <p> 描述 : 声明一个容器注解类
* @author : blackcat
* @date : 2020/8/7 10:38
*/
@Retention(RetentionPolicy.RUNTIME)
public @interface Schedules {
Schedule[] value();
}
package com.blackcat.metaannotation.repeatable;
import java.lang.reflect.Method;
/**
* <p> 描述 : 测试
* @author : blackcat
* @date : 2020/8/7 10:38
*/
@Schedule(dayOfMonth="2月")
@Schedule(dayOfWeek="2周", hour=24)
public class TestRepetableAnnotation {
@Schedule(dayOfMonth="3月")
@Schedule(dayOfWeek="3周", hour=23)
public void doPeriodicCleanup(){}
public static void main(String[] args) throws NoSuchMethodException {
Method doPeriodicCleanup = TestRepetableAnnotation.class.getMethod("doPeriodicCleanup");
Schedules schedules = doPeriodicCleanup.getAnnotation(Schedules.class);
System.out.println("获取标记方法上的重复注解:");
for (Schedule schedule: schedules.value()){
System.out.println(schedule);
}
System.out.println("获取标记类上的重复注解:");
if (TestRepetableAnnotation.class.isAnnotationPresent(Schedules.class)){
schedules = TestRepetableAnnotation.class.getAnnotation(Schedules.class);
for (Schedule schedule: schedules.value()){
System.out.println(schedule);
}
}
}
}
打印结果:
获取标记方法上的重复注解:
@com.blackcat.metaannotation.repeatable.Schedule(hour=12, dayOfMonth=3月, dayOfWeek=1周)
@com.blackcat.metaannotation.repeatable.Schedule(hour=23, dayOfMonth=1月, dayOfWeek=3周)
获取标记类上的重复注解:
@com.blackcat.metaannotation.repeatable.Schedule(hour=12, dayOfMonth=2月, dayOfWeek=1周)
@com.blackcat.metaannotation.repeatable.Schedule(hour=24, dayOfMonth=1月, dayOfWeek=2周)
打印结果说明:
示例:方法上的@Schedule(dayOfMonth="3月")
。其中dayOfMonth="3月"
,而hour
与dayOfWeek
则为@Schedule
注解里的默认值。
其他打印结果类似。