注解

10/20/2022 Java

摘要

JDK:1.8.0_202

# 一:基本Annotation

  • @Override:指定方法覆盖,用来帮程序员避免一些低级错误,只能修饰方法;
  • @Deprecated:表示某个程序元素(类、方法等)已过时,当其他程序使用已过时类、方法时,编译器将会给出警告;
  • @SuppressWarnings:被该Annotation修饰的程序元素(以及该程序元素中的所有子元素)取消显示指定的编译器警告;
  • @FunctionalInterface:指定某个接口必须是函数式接口。

@Deprecated的作用与文档注释中的@deprecated标记的作用基本相同,但它们的用法不同,前者是JDK 5才支持的注解,无须放在文档注释语法(/**…*/部分)中,而是直接用于修饰程序中的程序单元,如方法、类、接口等。

示例一:

// 关闭整个类里的编译器警告
@SuppressWarnings(value = "unchecked")
public class Test {

    // 重写 Object 类 toString 方法
    @Override
    public String toString() {
        return "Test{}";
    }

    // 定义 info 方法已过时
    @Deprecated
    public void deprecated() {
        System.out.println("方法已过时");
    }

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

示例二:

/**
 * Java 8规定:如果接口中只有一个抽象方法(可以包含多个默认方法或多个static方法),该接口就是函数式接口。
 * @FunctionalInterface就是用来指定某个接口必须是函数式接口。
 */
@FunctionalInterface
public interface FunInterface {

    static void foo() {
        System.out.println("foo 类方法");
    }

    default void bar() {
        System.out.println("bar 默认方法");
    }

    // 抽象方法
    void test();
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

# 二:自定义 Annotation

定义新的Annotation类型使用 @interface 关键字。Annotation 的成员变量在 Annotation 定义中以无形参的方法形式来声明,其方法名和返回值定义了该成员变量的名字和类型。

// 指定该注解信息会保留到运行时 - 下面内容会讲解
@Retention(RetentionPolicy.RUNTIME)
// 作用在方法或成员变量上 - 下面内容会讲解
@Target({ElementType.METHOD, ElementType.FIELD})
public @interface MyTag {

    // 定义成员变量
    String name();

    // 定义成员变量指定初始值
    int age() default 18;

}


public class Test1 {

    @MyTag(name = "xx")
    public void info1() {
    }

    @MyTag(name = "xx", age = 22)
    public void info2() {
    }

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26

# 三:元 Annotation

java.lang.annotation 包下提供了6个Meta Annotation(元Annotation),其中有5个元Annotation都用于修饰其他的Annotation定义。

# 3.1 @Retention

@Retention 只能用于修饰 Annotation 定义,用于指定被修饰的 Annotation 可以保留多长时间,@Retention包含一个RetentionPolicy类型的value成员变量,所以使用@Retention时必须为该value成员变量指定值。

  • RetentionPolicy.CLASS:编译器将把Annotation记录在class文件中。当运行Java程序时,JVM不可获取Annotation信息。这是默认值;

  • RetentionPolicy.RUNTIME:编译器将把Annotation记录在class文件中。当运行Java程序时,JVM也可获取Annotation信息,程序可以通过反射获取该Annotation信息;

  • RetentionPolicy.SOURCE:Annotation只保留在源代码中,编译器直接丢弃这种Annotation。

示例:

@Retention(value= RetentionPolicy.RUNTIME)
public @interface Testable{}

// 或省略Value

@Retention(RetentionPolicy.RUNTIME)
public @interface Testable{}
1
2
3
4
5
6
7

# 3.2 @Target

@Target 也只能修饰一个 Annotation 定义,它用于指定被修饰的 Annotation 能用于修饰哪些程序单元。@Target 元Annotation也包含一个名为value的成员变量,该成员变量的值只能是如下几个。

  • ElementType.ANNOTATION_TYPE:指定该策略的Annotation只能修饰Annotation;
  • ElementType.CONSTRUCTOR:指定该策略的Annotation只能修饰构造器;
  • ElementType.FIELD:指定该策略的Annotation只能修饰成员变量;
  • ElementType.LOCAL_VARIABLE:指定该策略的Annotation只能修饰局部变量;
  • ElementType.METHOD:指定该策略的Annotation只能修饰方法定义;
  • ElementType.PACKAGE:指定该策略的Annotation只能修饰包定义;
  • ElementType.PARAMETER:指定该策略的Annotation可以修饰参数;
  • ElementType.TYPE:指定该策略的Annotation可以修饰类、接口(包括注解类型)或枚举定义。
@Target(value = ElementType.FIELD)
@interface TargetTest{}

// 或省略Value

@Target(ElementType.FIELD)
@interface TargetTest{}

// 指定多个
@Target({ElementType.FIELD, ElementType.METHOD})
@interface TargetTest {}
1
2
3
4
5
6
7
8
9
10
11

# 3.3 @Documented

@Documented 用于指定被该元Annotation修饰的Annotation类将被javadoc工具提取成文档,如果定义Annotation类时使用了 @Documented 修饰,则所有使用该Annotation修饰的程序元素的API文档中将会包含该Annotation说明。

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
// 定义 Testable Annotation 将被 javadoc 工具提取
@Documented
public @interface Testable {
}


public class DocumentedTest {

    // 使用 @Testable 修饰 info() 方法
    @Testable
    public void info() {
        System.out.println("info 方法...");
    }

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

# 3.4 @Inherited

@Inherited 元Annotation指定被它修饰的Annotation将具有继承性——如果某个类使用了@Xxx注解(定义该Annotation时使用了@Inherited修饰)修饰,则其子类将自动被@Xxx修饰

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Inherited
public @interface Iht {
}


// 使用 @Iht 修饰的 Base 类
@Iht
class Base {
}


public class InheritableTest extends Base {

    public static void main(String[] args) {
        // 打印 InheritableTest 类是否有 @Inheritable 修饰
        System.out.println(InheritableTest.class.isAnnotationPresent(Iht.class));   // true
    }

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

# 3.5 @Repeatable

在Java 8以前,同一个程序元素前最多只能使用一个相同类型的Annotation;如果需要在同一个元素前使用多个相同类型的Annotation,则必须使用Annotation "容器",例如下面。

@Results({@Result(name="failure", location="failed.jsp"),
		  @Result(name="success", location="success.jpp")})
public Action FooAction {...}
1
2
3

上面代码中使用了两个 @Result 注解,但由于传统Java语法不允许多次使用 @Result 修饰同一个类,因此程序必须使用 @Results 注解作为两个@Result的容器。

从Java 8开始,上面语法可以得到简化:Java 8允许使用多个相同类型的Annotation来修饰同一个类,因此上面代码可能(之所以说可能,是因为重复注解还需要对原来的注解进行改造)可简化为如下形式:

@Result(name="failure", location="failed.jsp")
@Result(name="success", location="success.jpp")
public Action FooAction {...}
1
2
3

开发重复注解需要使用 @Repeatable 修饰,下面通过示例示范:

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Repeatable(FkTags.class)
public @interface FkTag {

    String name() default "ccj";

    int age();

}
1
2
3
4
5
6
7
8
9
10

上面注解默认不能作为重复注解使用,如果使用两个以上的该注解修饰同一个类,编译器会报错。为了将该注解改造成重复注解,需要使用 @Repeatable 修饰该注解,使用 @Repeatable 时必须为value成员变量指定值,该成员变量的值应该是一个 "容器" 注解——该 "容器" 注解可包含多个 @FkTag,因此还需要定义如下的 "容器" 注解:

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface FkTags {
    // 定义 Value 成员变量,该成员变量可接受多个 @FkTag 注解
    FkTag[] value();
}
1
2
3
4
5
6

"容器" 注解的保留期必须比它所包含的注解的保留期更长,否则编译器会报错。

修改 @FkTag 注解例子的代码:



 








@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Repeatable(FkTags.class)
public @interface FkTag {

    String name() default "ccj";

    int age();

}
1
2
3
4
5
6
7
8
9
10

测试类如下:

@FkTag(age = 18)
@FkTag(name = "cc", age = 20)
public class FkTagTest {

    public static void main(String[] args) {
        Class<FkTagTest> clazz = FkTagTest.class;
        // 使用 Java8 新增的 getDeclaredAnnotationsByType() 方法获取
        FkTag[] tags = clazz.getDeclaredAnnotationsByType(FkTag.class);
        // 遍历修饰 FkTagTest 类的多个 @FkTag 注解
        for (FkTag tag : tags) {
            System.out.println(tag.name() + "-->" + tag.age());
        }

        // 使用传统的 getDeclaredAnnotation() 方法获取
        FkTags container = clazz.getDeclaredAnnotation(FkTags.class);
        System.out.println(container);
    }

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

实际上,系统依然将两个 @FkTag 注解作为 @FkTags 的value成员变量的数组元素。只是一个简化写法。

# 四:Type Annotation

Java 8为ElementType枚举增加了 TYPE_PARAMETER、TYPE_USE 两个枚举值,这样就允许定义枚举时使用 @Target(ElementType.TYPE_USE) 修饰,这种注解被称为 Type Annotation(类型注解),Type Annotation 可用在任何用到类型的地方。

从Java 8开始,Type Annotation 可以在任何用到类型的地方使用。比如,允许在如下位置使用Type Annotation:

  • 创建对象(用new关键字创建)。
  • 类型转换。
  • 使用implements实现接口。
  • 使用throws声明抛出异常。

上面这些情形都会用到类型,因此都可以使用类型注解来修饰。

// 定义一个简单的 Type Annotation,不带任何成员变量
@Target(ElementType.TYPE_USE)
@interface NotNull {
}

public class TypeAnnotationTest implements @NotNull /* implements 时使用 Type Annotation*/ Serializable {

    // 方法形参中使用 Type Annotation
    public static void main(@NotNull String[] args)
    // throws 时使用 Type Annotation
            throws @NotNull FileNotFoundException {
        Object obj = "ccj";
        // 强制类型转换时使用 Type Annotation
        String str = (@NotNull String) obj;
        // 创建对象时使用 Type Annotation
        Object win = new @NotNull StringBuilder();
    }

    public void foo(List<@NotNull String> info) {
    }

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

# 五:参考文献

  • 《疯狂Java讲义(第三版) - 李刚》
最后更新: 10/20/2022, 9:14:55 PM