Java 注解

Java 注解是程序源代码的元数据。在本教程中,我们将通过示例了解什么是 Java 注解、常见的注解类以及以及如何创建一个自定义注解。

在本教程中,我们将通过示例了解什么是 Java 注解、常见的注解类以及以及如何创建一个自定义注解。

Java 注解

Java 注解是程序源代码的元数据(关于数据的数据)。它们向编译器提供有关程序的附加信息。注解不影响编译程序的执行。注解的英文术语是 annotation。

使用注解以 @ 开头,语法是: @AnnotationName@AnnotationName()

我们用 @Override 注解举例说明以下:

@Override 注解用在子类方法上,用以标示该方法要覆盖父类的方法。@Override 注解是向编译器传达信息,如果子类的方法不符合覆盖方法的要求或者规则,编译器在编译时会给出错误提示。

示例 1:@Override 注解示例

class Animal {
  public void displayInfo() {
    System.out.println("I am an animal.");
  }
}

class Dog extends Animal {
  @Override
  public void displayInfo() {
    System.out.println("I am a dog.");
  }
}

public class Main {
  public static void main(String[] args) {
    Animal d1 = new Dog();
    d1.displayInfo();
  }
}

输出

I am a dog.

在此示例中,该方法 displayInfo() 同时存在于 Animal 超类中和 Dog 子类中. 调用此方法时,将调用子类的方法而不是超类中的方法。

如果我们声明了 @Override 注解,却不是符合覆盖方法的规则,则编译器会给出错误。比如我们上 Dog 修改如下:

class Dog extends Animal {
  @Override
  public void displayInfo11111() {
    System.out.println("I am a dog.");
  }
}

则运行 javac Main.java 编译 Main.java 的时候,返回以下编译错误:

Main.java:8: 错误: 方法不会覆盖或实现超类型的方法
  @Override
  ^
1 个错误

注解格式

注解还可以自己的参数/参数/成员。根据参数的多少,注解可分为:

  1. 标记注解,没有参数,相当于一个标记。
  2. 单参数注解
  3. 多参数注解

标记注解

标记注解不包含参数/参数/成员。它仅用于标记声明。

它的语法是: @AnnotationName@AnnotationName()

由于这些注解不包含参数,因此可以排除括号。例如: @Override

单参数注解

单参数注解仅包含一个参数。

它的语法是: @AnnotationName(elementName = "elementValue")

如果只有一个参数,且当参数名是 value 时可以省略参数名称, 比如:

@AnnotationName(value = "elementValue")

可以简化为:

@AnnotationName("elementValue")

多参数注解

这些注解包含多个参数,参数之间以逗号分隔。

它的语法是: @AnnotationName(element1 = "value1", element2 = "value2")

当注解存在多个参数时,参数名不可以省略。

注解的位置

可以在任何声明之前/之上(上一行)使用注解。从 Java 8 开始,注解也可以放在类型之前。

1. 声明注解

如上所述,Java 注解可以放置在类、方法、接口、字段和其他程序参数声明之前。

示例 2:@SuppressWarnings 注解示例

import java.util.*;

public class Main {
  @SuppressWarnings("unchecked")
  static void wordsList() {
    ArrayList wordList = new ArrayList<>();

    // This causes an unchecked warning
    wordList.add("gobeta");

    System.out.println("Word list => " + wordList);
  }

  public static void main(String args[]) {
    wordsList();
  }
}

输出

Word list => [gobeta]

如果上面的程序是在不使用 @SuppressWarnings("unchecked") 注解的情况下编译的,编译器仍然会编译该程序,但会给出如下警告:

Main.java 使用未经检查或不安全的操作。
Word list => [gobeta]

我们收到警告: Main.java 使用未经检查或不安全的操作 ,是因为下面的语句:

ArrayList wordList = new ArrayList<>();

这是因为我们还没有定义 ArrayList 的泛型类型。我们可以通过在尖括号 <> 内指定泛型来修复此警告。

ArrayList<String> wordList = new ArrayList<>();

2. 类型注解

在 Java 8 之前,注解只能应用于声明。现在,也可以使用类型注解。这意味着我们可以在任何使用类型的地方放置注解。

  • 构造函数调用

    new @Readonly ArrayList<>()
    
  • 类型定义

    @NonNull String str;
    

    此声明指定非空 String 字符串变量要避免 NullPointerException 空指针异常。

    @NonNull List<String> newList;
    

    此声明指定了一个类型为 String 的非空列表。

    List<@NonNull String> newList;
    

    此声明指定了一个类型为 String 的非空值列表。

  • 类型转换

    newStr = (@NonNull String) str;
    
  • 扩展和实现子句

    class Warning extends @Localized Message
    
  • 抛出子句

    public String readMethod() throws @Localized IOException
    

类型注解可以更好地分析 Java 代码并提供更强大的类型检查。

注解的分类

根据注解的目的和使用使用时机,可以按如下分类:

  • 编译器指令 - 注解可用于向编译器提供指令、检测错误或抑制警告。内置的批注 @Deprecated@Override@SuppressWarnings 用于这些目的。
  • 编译时指令 - 这些注解提供的编译时指令帮助软件构建工具生成代码、XML 文件等。
  • 运行时指令 - 可以定义一些注解以在运行时向程序提供指令。这些注解是使用 Java 反射访问的。

Java 预定了一些编译器和编译时的指令,请查看 Java 预定义注解Java 元注解了解更多信息。

自定义注解

上面用到的都是系统预定义的注解,虽然这些注解能带给我们很大的帮助,但是某些场景下,我们需要自己开发注解。比如校验对象值、生成接口文档、权限控制等。

创建自定义注解的语法是:

public @interface AnnotationName {
  DataType MethodName() default defaultValue;
}

说明:

  • @interface 用来标识一个注解类型。
  • AnnotationName 是注解的名称,就像类名、接口名一样。
  • MethodName 类似于接口中的抽象方法,没有实现。方法名称就是注解的参数名称。
  • default 后面跟的是注解参数的默认值。默认值是可选的。
  • DataType 是方法的返回类型,可以是原始、枚举、字符串、类名或这些类型的数组。

示例 3:自定义注解示例

@interface MyCustomAnnotation {
  String value() default "default value";
}

public class Main {
  @MyCustomAnnotation(value = "gobeta")
  public void method1() {
    System.out.println("Test method 1");
  }

  public static void main(String[] args) throws Exception {
    Main obj = new Main();
    obj.method1();
  }
}

这个例子指示简单的展示了创建一个注解和标注注解。但是这个注解并没有实际的用途。我们创建了注解之后,一般会利用 Java 反射机制来获取这些注解信息,利用这些信息达到一些其他的目的,比如:校验对象值、生成接口文档等。这在稍后的课程中会讲解到。