Java5之后支持注解,注解其实是代码里的特殊标记,这些标记可以在编译、类加载、运行时被读取,并执行相应的处理。通过使用注解,程序开发人员可以在不改变原有逻辑的情况下,在源文件在嵌入一些补充信息。代码分析工具、开发工具和部署工具都可以通过这些补充信息进行验证和部署。注解是一种引用数据类型,注解定义在编译之后会生成相应的xxx.class文件。

一、基本注解(java.lang包)

5个基本注解如下:
@Override
@Deprecated
@SuppressWarnings
@SafeVarargs
@FunctionalInterface
其中@SafeVarargs是Java7新增的,@FunctionalInterface是Java8新增的。

1.限定重写父类方法@Override

@Override它强制一个子类必须重写父类的方法,告诉编译器进行检查,保证父类要包含一个被该方法重写的方法,否则就会编译出错。

  1. public class Fruit{
  2. public void info(){
  3. System.out.println("水果的info方法...");
  4. }
  5. }
  6. class Apple extends Fruit{
  7. @Override
  8. public void info(){
  9. System.out.println("重写的info方法...");
  10. }
  11. }

该注解的主要作用是帮程序员避免一些低级错误,例如把Apple中的info方法不小心写成了inf0,这样的低级错误可能会成为后期排错时的巨大障碍,因为这种错误在编译、运行时都没有任何提示,只是在运行时出现不一样的结果。
注:@Override只能修饰方法,不能修饰其他程序元素。

2.Java9增强的@Deprecated

@Deprecated用于表示某个程序的元素(类、方法等)已过时,当其他程序使用已过时的类、方法时,编译器将给出警告。Java9为@Deprecated注解增加了如下两个属性:
forRemoval:表示该API在将来是否会被删除。
since:表示该API从哪个版本被标记为过时。

  1. public class Fruit{
  2. @Deprecated(since = "9", forRemoval = true)
  3. public void info(){
  4. System.out.println("水果的info方法...");
  5. }
  6. }

3.抑制编译器警告@SuppressWarnings

@SuppressWarnings指示被该注解修饰的程序元素(以及该程序元素中的所有子元素)取消显示指定的编译器警告。该注解会一直作用于该程序元素的所有子元素(叠加效应)。例如,使用该注解修饰某个类取消显示某个编译器警告,同时又修饰该类里的某个方法取消显示另一个编译器警告,那么该方法将会同时取消显示这两个编译器警告。

  1. //关闭整个类里的编译器警告
  2. @SuppressWarnings(value = "unchecked")
  3. public class Test {
  4. public static void main(String[] args) {
  5. List<String> list = new ArrayList();//不使用注解将引起编译器警告
  6. }
  7. }

注:当使用该注解来关闭警告时,一定要在括号里使用name=value的形式为该注解的成员变量设置值。

4.堆污染警告与Java9增强的@SafeVarargs

5.函数式接口与@FunctionalInterface

从Java8开始,如果接口中只有一个抽象方法(可以包含多个默认方法或多个static方法),该接口就是函数式接口。@FunctionalInterface就是用来指定某个接口必须是函数式接口。
函数式接口就是为Java8的Lambda表达式准备的,Java8允许使用Lambda表达式创建函数式接口的实例,因此Java8专门增加了@FunctionalInterface注解。

  1. @FunctionalInterface
  2. public interface FunInterface{
  3. static void f1(){
  4. System.out.println("类方法...");
  5. }
  6. default void f2(){
  7. System.out.println("默认方法...");
  8. }
  9. void test();//只定义一个抽象方法
  10. }

该注解告诉编译器检查这个接口,保证该接口只包含一个抽象方法,否则就会编译出错,主要用来帮助程序员避免一些低级错误。
注:@FunctionalInterface只能修饰接口,不能修饰其他程序元素。

二、JDK的元注解(java.lang.annotation包)

JDK除在java.lang下提供了5个基本的注解之外,还在java.lang.annotation包下提供了6个Meta注解(元注解),其中有5个元注解都用于修饰其他注解定义。此处介绍常用的4个元注解。

1.@Retention

该注解只能修饰注解定义,用来指定被修饰的注解可以保留多长时间,它包含一个value成员变量,使用@Retention时必须为该value成员变量指定值。value成员变量的值只能是如下三个:

  • RetentionPolicy.CLASS:编译器把注解记录在class文件中,当运行Java程序时,JVM不可以获取注解信息,这是默认值。
  • RetentionPolicy.RUNTIME:编译器将把注解记录在class文件中。当运行Java程序时,JVM可获取注解信息,程序也可以通过反射获取该注解信息。
  • RetentionPolicy.SOURCE:注解只保留在源代码中,编译器直接丢弃这种注解。

使用@Retention元注解可采用如下代码为value指定值:

  1. //方式一
  2. @Retention(value = RetentionPolicy.RUNTIME)
  3. public @interface Test{
  4. //自定义注解
  5. }
  6. //方式二
  7. @Retention(RetentionPolicy.RUNTIME)
  8. public @interface Test{
  9. //自定义注解
  10. }

注:如果使用注解时只需要为value成员变量指定值,则使用该注解时可以直接在该注解后的括号里指定value成员变量的值,无须使用“value = 变量值”的形式(value的省略)。此方式只针对名为value的成员变量有效,且只有value这一个属性。若注解的成员变量名不为value或有多个属性,赋值时不可省略value,只能使用变量名 = 变量值的方式。

2.@Target

该注解只能修饰注解定义,它用于指定被修饰的注解能用于修饰哪些程序元素。该元注解包含一个value成员变量,该成员变量的值只能是如下几个:

  • ElementType.ANNOTATION_TYPE:指定该注解只能修饰注解。
  • ElementType.CONSTRUCTOR:指定该注解只能修饰构造器。
  • ElementType.FIELD:指定该注解只能修饰成员变量。
  • ElementType.LOCAL_VARIABLE:指定该注解只能修饰局部变量。
  • ElementType.METHOD:指定该注解只能修饰方法定义。
  • ElementType.PACKAGE:指定该注解只能修饰包定义。
  • ElementType.PARAMETER:指定该注解只能修饰参数。
  • ElementType.TYPE:指定该注解可以修饰类、接口或枚举定义。
    1. @Target(value = ElementType.FIELD)
    2. public @interface Test{
    3. //该注解只能修饰成员变量
    4. }
    5. @Target(ElementType.METHOD)
    6. public @interface Test{
    7. //该注解只能修饰方法
    8. }

    3.@Documented

    该注解用于指定被该元注解修饰的注解类将被javadoc工具提取成文档,如果定义注解类时使用了@Documented修饰,则所有使用该注解修饰的程序元素的API文档中将会包含该注解说明。 ```java @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.METHOD) @Documented public @interface Test{

}

  1. <a name="nLDVQ"></a>
  2. ## 4.@Inherited
  3. 该注解指定被它修饰的注解将具有继承性—如果某个类使用了@XXX注解(定义该注解时使用了@Inherited修饰)修饰,则其子类将自动被@XXX修饰。
  4. ```java
  5. @Retention(RetentionPolicy.RUNTIME)
  6. @Target(ElementType.TYPE)
  7. @Inherited
  8. public @interface Test{
  9. //如果某个类被Test注解修饰,则其子类也被Test修饰,Test注解具有继承性
  10. }
  11. @Test
  12. class A{
  13. }
  14. class B extends A{
  15. public static void main(String[] args) {
  16. //打印B类是否有@Test修饰
  17. System.out.println(B.class.isAnnotationPresent(Test.class));
  18. }
  19. }
  20. //true

三、自定义注解

1.定义注解

  1. //定义一个简单的注解类型
  2. public @interface Test{
  3. }

注解不仅可以是这种简单的注解,还可以带成员变量,成员变量在注解定义中以无形参的方法形式来声明,其方法名和返回值定义了该成员变量的名字和类型。

  1. //定义带成员变量的注解类型
  2. public @interface Test{
  3. String name();
  4. int age();
  5. }

一旦在注解里定义了成员变量,则在使用该注解时就应该为它的成员变量指定值。可以在定义注解的成员变量时为其指定初始值(默认值),指定成员变量的初始值可使用default关键字。如果为注解的成员变量指定了默认值,使用该注解时就可以不为成员变量指定值,而是直接使用默认值。

  1. public @interface Test{
  2. String name() default "sundegan";
  3. int age() default 23;
  4. }
  5. //注解的成员变量名不为value,赋值时不可省略value,只能使用变量名 = 变量值的方式
  6. @Test(name = "a", age = 1)
  7. class A{
  8. }

根据注解是否包含成语变量,可以把注解分为两大类:

  • 标记注解:没有定义成员变量的注解类型被称为标记注解,这种注解仅利用自身的存在与否来提供信息,如@Override。
  • 元数据注解:包含成员变量的注解,可以接受更多的元数据,所以被称为元数据注解。

    2.注解中的属性类型

    注解中的属性类型可以是byte, short, int, long, float, double, boolean, char, String, Class, 枚举。以及以上每一种的数组形式。

    1. enum Season{
    2. SPRING,SUMMER,FALL,WINTER
    3. }
    4. @Retention(RetentionPolicy.RUNTIME)
    5. @Target(ElementType.TYPE)
    6. public @interface Test{
    7. int age();
    8. String[] email();
    9. Season[] season();
    10. }
    11. //数组赋值需用大括号
    12. @Test(age = 1, email = {"zhansan@qq.com", "zhansan@163.com"}, season = {Season.SPRING,Season.FALL})
    13. class A{
    14. }
    15. //若数组中只有一个值,大括号可以省略
    16. @Test(age = 1, email = "zhansan@qq.com", season = Season.FALL)
    17. class B{
    18. }

    3.反射提取注解信息

    3.1 反射注解基础

    Java使用java.lang.annotation.Annotation接口来代表程序元素前面的注解,该接口是所有注解的父接口。Java5在java.lang.reflect包下新增了AnnotatedElement接口,该接口代表程序中可以接受注解的程序元素。该接口主要有以下几个实现类:

  • Class:类定义

  • Constructor:构造器定义
  • Field:类的成员变量定义
  • Method:类的方法定义
  • Package:类的包定义

AnnotatedElement接口是所有程序元素(如Class、Method、Constructor等)的父接口,所以程序通过反射获取了某个类的AnnotatedElement对象(如Class、Method、Constructor等)之后,程序就可以调用该对象的如下几个方法来访问注解信息:

class A{ @Deprecated @Test(username = “admin”, password = “123”) public void fun(){

  1. }

} class ReflectAnnotation{ public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException { Annotation[] annotations = Class.forName(“com.sundegan.A”).getMethod(“fun”).getAnnotations(); for (var a : annotations) { System.out.println(a); } } } //@java.lang.Deprecated(forRemoval=false, since=””) //@com.sundegan.Test(username=”admin”, password=”123”)

<a name="U7sfq"></a>
### 3.2 获取类上面的注解信息

- 获取类
- 判断该类上面是否有该注解
- 通过类获取注解对象
- 获取注解对象的属性
```java
//表示该注解可以被反射
@Retention(RetentionPolicy.RUNTIME)
//表示该注解可以用于类和方法
@Target({ElementType.TYPE, ElementType.METHOD})
public @interface Test{
    int age() default 1;
}
@Test
class A{

}
class ReflectAnnotation{
    public static void main(String[] args) throws ClassNotFoundException {
        //1.获取类
        Class c = Class.forName("com.sundegan.A");
        //2.判断类上面是否有注解@Test
        System.out.println(c.isAnnotationPresent(Test.class));
        if (c.isAnnotationPresent(Test.class)){
            //3.如果有,则获取该注解对象
            Test t = (Test)c.getAnnotation(Test.class);
            //4.获取注解对象的属性
            System.out.println(t.age());
        }
    }
}

3.3 获取方法上的注解信息

  • 获取类
  • 获取类中的方法
  • 判断该方法上是否有该注解
  • 获取注解对象
  • 通过注解对象获取注解信息 ```java //表示该注解可以被反射 @Retention(RetentionPolicy.RUNTIME) //表示该注解可以用于类和方法 @Target({ElementType.TYPE, ElementType.METHOD}) public @interface Test{ String username(); String password(); }

class A{ @Test(username = “admin”, password = “123”) public void fun(){

}

} class ReflectAnnotation{ public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException { //1.获取类 Class c = Class.forName(“com.sundegan.A”); //2.获取类中的fun()方法 Method method = c.getDeclaredMethod(“fun”); //Method method = Class.forName(“com.sundegan.A”).getMethod(“fun”); //3.判断该方法上是否有@Test注解 if (method.isAnnotationPresent(Test.class)){ //4.获取注解对象 Test test = (Test) method.getAnnotation(Test.class); //5.通过注解对象获取注解信息 System.out.println(test.username()); System.out.println(test.password()); } } } ```

3.注解在开发中的作用

注解本身并不起作用,一定需要注解处理工具APT(Annotation Processing Tool)才能发挥作用,它对源代码文件进行检测,并找出源文件所包含的注解信息,然后对该注解信息进行额外的处理,使用APT的主要目的是简化开发者的工作量,可以代替传统的对代码信息和附属文件的维护工作。
如:
1.假设有一个@Id注解,这个注解只能修饰类,要求类中必须有一个int类型的id属性,如果没有则报异常,如果有则正常执行。
2.使用注解可以在编译程序源代码的同时生成一些附属文件(如类文件、程序发布描述文件等),这些附属文件的内容也都与源代码相关。

四、总结

重点掌握@Override,@Deprecated,@Retention,@Target这四个注解,会自己定义注解以及会使用注解,能看懂源码中的注解使用。