Java Review三十五、注解)

网友投稿 611 2022-05-30

文章目录

基本注解

JDK 的元注解

@Retention

@Target

@Documented

@lnherited

自定义注解

提取注解信息

使用注解实例

Demo1

Demo2

基本注解

Java 提供了5 个基本注解:

@Override:让编译器检查该方法是否正确地实现了覆写。

@Deprecated:用于表示某个程序元素( 类、 方法等) 己过时, 当其他程序使用己过时的类、 方法时,编译器将会给出警告。

@SuppressWamings:告诉编译器忽略此处代码产生的警告。

@SafeVarargs:在声明具有模糊类型(比如:泛型)的可变参数的构造函数或方法时,Java编译器会报unchecked警告。鉴于这些情况,如果程序员断定声明的构造函数和方法的主体不会对其varargs参数执行潜在的不安全的操作,可使用@SafeVarargs进行标记,这样的话,Java编译器就不会报unchecked警告。

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

JDK 的元注解

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

@Retention

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

value 成员变量的值只能是如下三个:

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

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

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

如 果 需 要 通 过 反 射 获 取 注 解 信 息 , 就 需 要 使 用 value 属 性 值 为 RetentionPolicy.RUNTIME 的@Retention。

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

// 定义下面的(testable 注解保留到运行时 @Retention(value= RetentionPolicy.RUNTIME) public @interface Testable{}

1

2

3

也可采用如下代码来为 value 指定值:

// 定义下面的(testable 注解将被编译器直接丢弃 @Retention(RetentionPolicy.SOURCE) public @interface Testable{}

1

2

3

java.lang.annotation.Retention

@Target

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

ElementType.ANNOTATION_TYPE 指定该策略的注解只能修饰注解。

ElementType.CONSTRUCTOR 指定该策略的注解只能修饰构造器。

ElementType.FIELD 指定该策略的注解只能修饰成员变量。

ElementType.LOCAL_VARIABLE 指定该策略的注解只能修饰局部变量。

ElementType.METHOD 指定该策略的注解只能修饰方法定义。

ElementType.PACKAGE 指定该策略的注解只能修饰包定义。

ElementType.PARAMETER 指定该策略的注解可以修饰参数。

ElementType.TYPE 指定该策略的注解可以修饰类、 接口(包括注解类型) 或枚举定义。

如下代码指定@ActionListenerFor 注解只能修饰成员变量:

©Target(ElementType.FIELD) public @interface ActionListenerFor{}

1

2

如下代码片段指定@Testable 注解只能修饰方法:

@Target(ElementType.METHOD) public @interface Testable { }

1

2

java.lang.annotation.Target

@Documented

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

java.lang.annotation.Documented

@lnherited

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

java.lang.annotation.Inherited

自定义注解

Java语言使用@interface语法来定义注解(Annotation),格式如下:

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

1

2

3

4

在默认情况下, 注解可用于修饰任何程序元素, 包括类、 接口、 方法等, 如下程序使用@Test 来修饰方法:

public class MyClass{ // 使用@Test 注解修饰方法 @Test public void info(){ } }

1

2

3

4

5

6

7

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

如下代码可以定义一个有成员变量的注解:

public @interface MyTag{ // 定义带两个成员变量的注解 // 注解中的成员变量以方法的形式来定义 String name(); int age(); }

1

2

3

4

5

6

注解的参数类似无参数方法,可以用default设定一个默认值(强烈推荐)。最常用的参数应当命名为value:

public @interface MyTag{ // 定义带两个成员变量的注解 // 注解中的成员变量以方法的形式来定义 String name() default "牛钢铁"; int age() 666; }

1

2

3

4

5

6

也可以在使用 MyTag 注解时为成员变量指定值, 如果为 MyTag 的成员变量指定了值, 则默认值不会起作用:

public class MyTest{ @MyTag(name="麻球", age=6) public void info(){ } }

1

2

3

4

5

6

通常会用元注解去修饰自定义注解,如上文所示。

例如,使用@Target可以定义Annotation能够被应用于源码的哪些位置:

//定义MyTag注解应用于方法上 @Target(ElementType.METHOD) public @interface MyTag{ // 定义带两个成员变量的注解 // 注解中的成员变量以方法的形式来定义 String name() default "牛钢铁"; int age() 666; }

1

2

3

4

Java Review(三十五、注解)

5

6

7

8

提取注解信息

使用注解修饰了类、 方法、 成员变量等成员之后, 这些注解不会自己生效, 必须由开发者提供相应的工具来提取并处理注解信息。

因为注解定义后也是一种class,所有的注解都继承自 java.lang.annotation.Annotation,因此,读取注解,需要使用反射API。

Java提供的使用反射API读取Annotation的方法包括:

判断某个注解是否存在于Class、Field、Method或Constructor:

Class.isAnnotationPresent(Class)

Field.isAnnotationPresent(Class)

Method.isAnnotationPresent(Class)

Constructor.isAnnotationPresent(Class)

例如:

// 判断@Report是否存在于Person类: Person.class.isAnnotationPresent(Report.class);

1

2

使用反射API读取Annotation:

Class.getAnnotation(Class)

Field.getAnnotation(Class)

Method.getAnnotation(Class)

Constructor.getAnnotation(Class)

例如:

// 获取Person定义的@Report注解: Report report = Person.class.getAnnotation(Report.class); int type = report.type(); String level = report.level();

1

2

3

4

使用反射API读取Annotation有两种方法。方法一是先判断Annotation是否存在,如果存在,就直接读取:

Class cls = Person.class; if (cls.isAnnotationPresent(Report.class)) { Report report = cls.getAnnotation(Report.class); ... }

1

2

3

4

5

第二种方法是直接读取Annotation,如果Annotation不存在,将返回null:

Class cls = Person.class; Report report = cls.getAnnotation(Report.class); if (report != null) { ... }

1

2

3

4

5

读取方法、字段和构造方法的Annotation和Class类似。但要读取方法参数的Annotation就比较麻烦一点,因为方法参数本身可以看成一个数组,而每个参数又可以定义多个注解,所以,一次获取方法参数的所有注解就必须用一个二维数组来表示。例如,对于以下方法定义的注解:

public void hello(@NotNull @Range(max=5) String name, @NotNull String prefix) { }

1

2

要读取方法参数的注解,我们先用反射获取Method实例,然后读取方法参数的所有注解:

// 获取Method实例: Method m = ... // 获取所有参数的Annotation: Annotation[][] annos = m.getParameterAnnotations(); // 第一个参数(索引为0)的所有Annotation: Annotation[] annosOfName = annos[0]; for (Annotation anno : annosOfName) { if (anno instanceof Range) { // @Range注解 Range r = (Range) anno; } if (anno instanceof NotNull) { // @NotNull注解 NotNull n = (NotNull) anno; } }

1

2

3

4

5

6

7

8

9

10

11

12

13

14

使用注解实例

Demo1

注解@Testable 没有任何成员变量,仅是一个标记注解,它的作用是标记哪些方法是可测试的:

import java.lang.annotation.*; // 使@Retention指定注解的保留到运行时 @Retention(RetentionPolicy.RUNTIME) // 使用@Target指定被修饰的注解可用于修饰方法 @Target(ElementType.METHOD) // 定义一个标记注解,不包含任何成员变量,即不可传入元数据 public @interface Testable { }

1

2

3

4

5

6

7

8

9

10

如下 MyTest 测试用例中定义了 8 个方法, 这 8 个方法没有太大的区别, 其中 4 个方法使用@Testable注解来标记这些方法是可测试的:

public class MyTest { // 使用@Testable注解指定该方法是可测试的 @Testable public static void m1() { } public static void m2() { } // 使用@Testable注解指定该方法是可测试的 @Testable public static void m3() { throw new IllegalArgumentException("参数出错了!"); } public static void m4() { } // 使用@Testable注解指定该方法是可测试的 @Testable public static void m5() { } public static void m6() { } // 使用@Testable注解指定该方法是可测试的 @Testable public static void m7() { throw new RuntimeException("程序业务出现异常!"); } public static void m8() { } }

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

27

28

29

30

31

32

33

34

35

36

37

Demo2

@Range注解,我们希望用它来定义一个String字段的规则——字段长度满足@Range的参数定义:

@Retention(RetentionPolicy.RUNTIME) @Target(ElementType.FIELD) public @interface Range { int min() default 0; int max() default 255; }

1

2

3

4

5

6

在某个JavaBean中,使用注解:

public class Person { @Range(min=1, max=20) public String name; @Range(max=10) public String city; }

1

2

3

4

5

6

7

定义了注解,本身对程序逻辑没有任何影响。必须编写代码来使用注解。这里,编写一个Person实例的检查方法,它可以检查Person实例的String字段长度是否满足@Range的定义:

void check(Person person) throws IllegalArgumentException, ReflectiveOperationException { // 遍历所有Field: for (Field field : person.getClass().getFields()) { // 获取Field定义的@Range: Range range = field.getAnnotation(Range.class); // 如果@Range存在: if (range != null) { // 获取Field的值: Object value = field.get(person); // 如果值是String: if (value instanceof String) { String s = (String) value; // 判断值是否满足@Range的min/max: if (s.length() < range.min() || s.length() > range.max()) { throw new IllegalArgumentException("Invalid field: " + field.getName()); } } } } }

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

参考:

【1】:《疯狂Java讲义》

【2】:廖雪峰的官方网站:使用注解

【3】:春晨:@SafeVarargs注解的使用

【4】:廖雪峰的官方网站:自定义注解

【5】:廖雪峰的官方网站:处理注解

Java

版权声明:本文内容由网络用户投稿,版权归原作者所有,本站不拥有其著作权,亦不承担相应法律责任。如果您发现本站中有涉嫌抄袭或描述失实的内容,请联系我们jiasou666@gmail.com 处理,核实后本网站将在24小时内删除侵权内容。

上一篇:知乎服务化的实践与思考
下一篇:mysqldump备份时的数据一致性问题--single-transaction
相关文章