Java Review(三十五、注解)

举报
三分恶 发表于 2021/04/22 23:53:18 2021/04/22
【摘要】 文章目录 基本注解JDK 的元注解@Retention@Target@Documented@lnherited 自定义注解提取注解信息使用注解实例Demo1Demo2 注解能被用来为程序元素( 类、 方法、 成员变量等) 设置元数据。 值得指出的是, 注解不影响程序代码的执行, 无论增加、 删除注解, 代码都始终如一地执行。 如果希望让程序...


注解能被用来为程序元素( 类、 方法、 成员变量等) 设置元数据。 值得指出的是, 注解不影响程序代码的执行, 无论增加、 删除注解, 代码都始终如一地执行。 如果希望让程序中的注解在运行时起一定的作用, 只有通过某种配套的工具对注解中的信息进行访问和处理, 访问和处理注解的工具统称 APT( Annotation Processing Tool )。

基本注解

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
  • 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 没有任何成员变量,仅是一个标记注解,它的作用是标记哪些方法是可测试的:

Testable.java

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】:廖雪峰的官方网站:处理注解

文章来源: blog.csdn.net,作者:三分恶,版权归原作者所有,如需转载,请联系作者。

原文链接:blog.csdn.net/sinat_40770656/article/details/103516894

【版权声明】本文为华为云社区用户转载文章,如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。