注解(Annotation)是Java代码里的特殊标记,比如:@Override、@Test等,其作用是:通过反射机制让其他程序根据注解信息来决定怎么执行该程序。具体的讲有三种作用:
注解可以用在类上、构造器上、方法上、成员变量上、参数上等位置。
Java SE API中预先定义了一组注释类型。一些注解类型由Java编译器使用,而另一些则适用于注解其他注解(你没看错)。
在java.lang包中预定义的注解类型有@Deprecated、@Override和SuppressWarnings。
@Deprecated 注释表示标记的元素已弃用,不应再使用。每当程序使用带有@Deprecated注释的方法、类或字段时,编译器都会生成警告。当元素被弃用时,还应使用Javadoc @Deprecated标记进行记录,如下例所示。在Javadoc注释和注释中使用at符号(@)并非巧合:它们在概念上是相关的。此外,请注意,Javadoc 标记以小写d开始,注解以大写D开始。
// Javadoc comment follows
/**
* @deprecated
* explanation of why it was deprecated
*/
@Deprecated
static void deprecatedMethod() { }
}
@Override注释通知编译器该元素旨在覆盖超类中声明的元素。虽然在重写方法时不强制使用此注释,但它有助于防止错误。如果一个标记为@Override的方法未能正确重写其超类中的一个方法,编译器将报错。
// mark method as a superclass method
// that has been overridden
@Override
int overriddenMethod() { }
@SuppressWarnings注解告诉编译器抑制在未加注本注解时会生成的特定警告。在下面的示例中,使用了已弃用的方法,编译器通常会生成警告。然而,在加注本注解后,会导致警告被抑制。
// use a deprecated method and tell
// compiler not to generate a warning
@SuppressWarnings("deprecation")
void useDeprecatedMethod() {
// deprecation warning
// - suppressed
objectOne.deprecatedMethod();
}
每个编译器警告都属于一个类别。Java语言规范列出了两个类别:弃用和未检查。未检查警告可能发生在与在泛型出现之前编写的遗留代码接口时。要抑制多个类别的警告,请使用以下语法:
@SuppressWarnings({"unchecked", "deprecation"})
@SafeVarargs 注解应用于方法或构造函数时,表明代码不会对其可变参数执行潜在的不安全操作。当使用此注解类型时,与可变参数用法相关的未检查警告将被抑制。
@FunctionalInterface 注解是在Java SE 8中引入的,用于指示类型声明是按照Java语言规范定义的功能接口。
用于注解其他注解的注解,叫做元注解。它们定义在在java.lang.annotation包中,JDK提供了多种元注释类型。
@Retention 指定了标记的注解如何存储:
@Documented 注解表示,只要使用指定的注解,这些元素都应该使用Javadoc工具进行记录。
@Target 注解标记另一个注解,以限制该注解可以应用于哪种Java元素。目标注释将以下元素类型之一指定为其值:
@Inherited 注释表示该注解类型可以从父类继承。(默认情况下不是这样的。)当用户查询该注解类型时,如果该类没有该类型的注解,则查询该类的父类以获取该注解类型。该注解仅适用于类声明。
@Repeatable 注解在Java SE 8中引入,表示标记的注解可以多次应用于相同的声明或类型使用。有关更多信息,请参阅重复注解。
自定义注解的格式:
public @interface 注解名称 {
public 属性类型 属性名称() default 默认值;
}
注意如果注解在定义时只有一个value属性,那么默认在使用这个注解时value属性名可以省略,value是一个特殊的属性名称。
如自定一个名为MyAnno的注解:
public @interface MyAnno {
public String name() default "无名氏";
public String gender() default "男";
public int age();
public String[] hobby();
}
使用自定义的注解去标记类:
@MyAnno(name="注解类", gender = "女", age = 21, hobby = {"唱歌","跳舞","看电视"})
public class AnnoTest {
public static void main(String[] args) {
// TODO
}
}
经过反编译可知,注解是一种继承了Annotation接口的特殊接口。如MyAnno反编译后是如下的代码:Annotation类位于java.lang.annotation包中
public interface MyAnno extends Annotation{
public abstract String name();
public abstract String gender();
public abstract int age();
}
元注解是指修饰注解的注解。Java中常见的有两个,分别是@Target和@Retention。
声明被修饰的注解能在哪些位置使用
@Target(ElementType.TYPE) |
|
如标记注解可以用在类和方法上:@Target({ElementType.TYPE, ElementType.METHOD})
声明注解的保留周期。
@Retention(RetentionPolicy.RUNTIME) |
|
如控制注解一直保留到运行时:@Retention(RetentionPolicy.RUNTIME)
就是判断类上、方法上、成员变量上是否存在注解,并把注解里的内容给解析出来。
指导思想:要解析谁上面的注解,就应该先拿到谁(对象)。
比如要解析类上面的注解,则应该先获取该类的Class对象,再通过Class对象解析其上面的注解。比如要解析成员方法上的注解,则应该获取到该成员方法的Method对象,再通过Method对象解析其上面的注解。
Class、Method、Field、Constructor都实现了AnnotatedElement接口,它们都拥有解析注解的能力。
AnnotatedElement接口提供了解析注解的方法 |
说明 |
public Annotation getDeclaredAnnotations() |
获取当前对象上的注解 |
public T getDeclaredAnnotation(Class |
获取指定的注解对象 |
public boolean isAnnotationPresent(Class |
判断当前对象上是否存在某个注解 |
需求如下:
①定义注解MyTest4,要求如
②定义一个类叫:Demo,在类中定义一个test1方法,并在该类和其方法上使用MyTest4注解
③定义AnnotationTest3测试类,解析Demo类中的全部注解。
定义MyTest4注解:
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target({ElementType.TYPE, ElementType.METHOD}) // 限制本注解使用在类和成员方法上
@Retention(RetentionPolicy.RUNTIME) // 指定注解的有效范围:一直到运行时
public @interface MyTest4 {
String value(); // 属性
double aaa() default 100; // 属性,默认值100
String[] bbb(); //属性
}
定义使用自定义注解的类Demo类:
/**
* 本类用于使用自定义的注解MyTest4
*/
@MyTest4(value = "值1", aaa = 234.5, bbb = {"abc","def","ghi"})
public class Demo {
@MyTest4(value = "值2", aaa = 123.4, bbb = {"aaa","bbb"})
public void test1(){
}
}
使用AnnotationTest3类获取加注在Demo类上的MyTest4注解的具体内容:
import java.lang.reflect.Method;
import java.util.Arrays;
/**
* 本类解析Demo类中的全部注解
*/
public class AnnotationTest3 {
public static void main(String[] args) throws Exception {
// 1.先得到Class对象
Class c = Demo.class;
// 2.解析类上的注解
// 判断类上是否包含了某个注解
if(c.isAnnotationPresent(MyTest4.class)){ // 使用isAnnotationPresent方法判断是否包含MyTest4注解
MyTest4 myTest4 = c.getDeclaredAnnotation(MyTest4.class); // 获取MyTest4对象
// 打印加在Demo类上的注解的内容
System.out.println(myTest4.value());
System.out.println(myTest4.aaa());
System.out.println(Arrays.toString(myTest4.bbb()));
}
System.out.println("-------------------");
// 3.解析方法上的注解
Method m = c.getDeclaredMethod("test1");
if(m.isAnnotationPresent(MyTest4.class)){ // 判断方法上是否有注解
MyTest4 myTest4 = m.getDeclaredAnnotation(MyTest4.class);
System.out.println(myTest4.value());
System.out.println(myTest4.aaa());
System.out.println(Arrays.toString(myTest4.bbb()));
}
}
}
// 输出结果为:
// 值1
// 234.5
// [abc, def, ghi]
// -------------------
// 值2
// 123.4
// [aaa, bbb]
模拟JUnit框架,运行加注注解的方法,实现步骤:定义若干个方法,只要加注了MyTestAnno注解,才会触发该方法执行。
需求
●定义若干个方法,只要加了MyTestAnno注解,就会触发该方法执行。
分析
①定义一个自定义注解MyTestAnno,只能注解方法,存活范围是一直都在。
②定义若干个方法,部分方法加上@MyTestAnno注解修饰,部分方法不加。
③模拟一个junit程序,可以触发加了@MyTestAnno注解的方法执行。
第一步,定义MyTestAnno注解:
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target(ElementType.METHOD) // 确定注解的使用范围
@Retention(RetentionPolicy.RUNTIME) // 确定注解的存活范围
// 定义注解
public @interface MyTestAnno {
}
第二步,编写使用MyTestAnno注解的类,该类有多个方法,有部分方法加注了MyTestAnno注解:
/**
* 本类用于模拟业务类,加注MyTestAnno注解的方法
* 会执行测试用例
* 在method2和method5上加注了注解
*/
public class MyUserfulClass {
public void method1(){
System.out.println("方法1");
}
@MyTestAnno
public void method2(){
System.out.println("方法2");
}
public void method3(){
System.out.println("方法3");
}
public void method4(){
System.out.println("方法4");
}
@MyTestAnno
public void method5(){
System.out.println("方法5");
}
}
第三步,定义MyJUnitSimulator类,用于模拟JUnit测试用例类,用于遍历MyUsefulClass中加注了MyTestAnno注解的方法:
import java.lang.reflect.Method;
public class MyJUnitSimulator {
public static void main(String[] args) throws Exception {
// 定义一个对象用于反射时调用invoke的触发对象
MyUserfulClass mu = new MyUserfulClass();
// 取得MyUserfualClass对象
Class mufc = MyUserfulClass.class;
// 遍历MyUserfualClass对象中的所有方法
for (Method m : mufc.getDeclaredMethods()) {
if(m.isAnnotationPresent(MyTestAnno.class)){ // 判断是否加注了MyTestAnno注释
m.invoke(mu); // 如果加注了MyTestAnno注释,则执行该方法
}
}
}
}
// 运行输出为:
// 方法2
// 方法5
在某些情况下,您希望将相同的注解应用于声明或类型使用。自 Java SE 8 版本以来,重复注解使您能够做到这一点。
例如,您正在编写代码以使用定时器服务,该服务使您能够在给定时间或特定时间运行方法,类似于UNIX cron服务。现在您想设置一个定时器,以便在每月最后一天和每个星期五晚上11点运行方法doPeriodicCleanup。要设置定时器运行,需要创建一个@Schedule注解并将其两次应用于doPeriodicCleanup方法。第一次使用指定每月最后一天,第二次指定星期五晚上11点,如下面的代码示例所示:
@Schedule(dayOfMonth="last")
@Schedule(dayOfWeek="Fri", hour="23")
public void doPeriodicCleanup() { ... }
上一个例子将注解应用于方法。您可以在使用任何标准注解的地方进行重复注解。例如,您有一个用于处理未经授权访问异常的类。您为经理或其他管理员使用一个@Alert注解:
@Alert(role="Manager")
@Alert(role="Administrator")
public class UnauthorizedAccessException extends SecurityException { ... }
出于兼容性原因,重复注解存储在Java编译器自动生成的容器注解中。为了使编译器能够执行此操作,代码中需要两个声明。
自定义的注解类型必须标记有@Repeatable元注解。以下示例定义了自定义的@Schedule可重复注解类型:
import java.lang.annotation.Repeatable;
@Repeatable(Schedules.class)
public @interface Schedule {
String dayOfMonth() default "first";
String dayOfWeek() default "Mon";
int hour() default 12;
}
元注解@Repeatable括号中的值(本例中为:Schedules.class)是Java编译器生成的、用于存储重复注解的容器注解(container annotation)的类型。 在此示例中,容器注解类型是Schedules,也即重复的@Schedule注解存储在@Schedules注解中。
在没有首先声明一个注解是可重复的情况下,将该注解重复应用于声明会导致编译时错误。
容器注解类型必须具有一个数组类型的值元素。可重复的数组类型必须是要包含的重复注释类型。容器注解类型的Schedules的声明如下:
public @interface Schedules {
Schedule[] value();
}
反射API中有几种方法可用于检索注释。返回单个注解的方法的行为(如 AnnotatedElement.getAnnotation(Class
在设计注解类型时,您必须考虑该类型注解的基数。现在,可以使用零次、一次或多次注解(如果注解的类型标记为@Repeatable)。还可以通过使用@Target元注解来限制注解类型的使用位置。例如,您可以创建一个只能用于方法和字段的可重复注解类型。重要的是要仔细设计注解类型,以确保使用注解的程序员能认为这些注解尽可能的灵活和强大。
在Java SE 8发布之前,注解只能应用于声明。从Java SE 8开始,注解也可以应用于任何类型。这意味着注解可以在任何使用类型的地方使用。比如,类实例创建表达式(new)、转换、实现子句和抛出子句。这种形式的注解称为类型注解。
创建类型注解是为了支持对Java程序进行改进的分析,以确保更强的类型检查。Java SE 8版本不提供类型检查框架,但它允许您编写(或下载)一个类型检查框架。例如,您希望确保程序中的特定变量永远不会被赋值为null;您希望避免触发NullPointerException。您可以编写一个自定义插件来检查这一点。然后,您可以修改代码来注释该特定变量,表明它永远不会被赋值为null。变量声明可能看起来像这样:
@NonNull String str;
当您在命令行编译代码(包括NonNull模块)时,如果编译器检测到潜在问题,则会打印一条警告,允许您修改代码以避免错误。在您更正代码以删除所有警告后,程序运行时将不会出现此特定错误。
您可以使用多个类型检查模块,每个模块检查不同类型的错误。通过这种方式,您可以在Java类型系统的基础上进行构建,在需要时添加特定的检查。
通过明智地使用类型注释和可插拔的类型检查器,您可以编写更强大、更不容易出错的代码。
在许多情况下,您不必编写自己的类型检查模块。有第三方已经完成了这项工作。
1.下面的接口定义有何错误?
public interface House {
@Deprecated
void open();
void openFrontDoor();
void openBackDoor();
}
2.考虑问题1中所示的House接口的实现为MyHouse。
public class MyHouse implements House {
public void open() {}
public void openFrontDoor() {}
public void openBackDoor() {}
}
如果你编译这个程序,编译器会生成一个警告,因为open被弃用了(在接口中)。你该如何来消除这个警告?
3.以下代码编译时是否会有错误?为什么?
public @interface Meal { ... }
@Meal("breakfast", mainDish="cereal")
@Meal("lunch", mainDish="pizza")
@Meal("dinner", mainDish="salad")
public void evaluateDiet() { ... }
4.为增强请求定义一个注解:包括元素id, synopsis, engineer, 和date。将engineer的默认值指定为unassigned
,将日期的默认值指定为unknown。
1.文档应该反映为什么弃用open方法,以及用什么来代替。例如
public interface House {
/**
* @deprecated use of open
* is discouraged, use
* openFrontDoor or
* openBackDoor instead.
*/
@Deprecated
public void open();
public void openFrontDoor();
public void openBackDoor();
}
2.你可以弃用open方法的实现:
public class MyHouse implements House {
// The documentation is
// inherited from the interface.
@Deprecated
public void open() {}
public void openFrontDoor() {}
public void openBackDoor() {}
}
或者,你也可以抑制警告:
public class MyHouse implements House {
@SuppressWarnings("deprecation")
public void open() {}
public void openFrontDoor() {}
public void openBackDoor() {}
}
3.代码无法编译。在 JDK 8 之前,不支持可重复的注释。从 JDK 8开始,由于 Meal 注释类型未被定义为可重复的,因此代码无法编译。可以通过添加 @Repeatable 元注释并定义容器注释类型来解决此问题。
public class AnnotationTest {
public @interface MealContainer {
Meal[] value();
}
@java.lang.annotation.Repeatable(MealContainer.class)
public @interface Meal {
String value();
String mainDish();
}
@Meal(value="breakfast", mainDish="cereal")
@Meal(value="lunch", mainDish="pizza")
@Meal(value="dinner", mainDish="salad")
public void evaluateDiet() { }
}
4.答案如下:
/**
* 描述了请求增强 (RFE) 注释类型。
* Describes the Request-for-Enhancement (RFE) annotation type.
*/
public @interface RequestForEnhancement {
int id();
String synopsis();
String engineer() default "[unassigned]";
String date() default "[unknown]";
}
参考资料:
1.【黑马磊哥】Java反射、注解、反射机制、反射专题、注解专题、挑战100个Java知识点,相信听完这套课,肯定可以解锁Java反射和注解
2.
https://docs.oracle.com/javase/tutorial/java/annotations/index.htmlhttps://docs.oracle.com/javase/tutorial/java/annotations/index.html