Java Review三十五、注解


@


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

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

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

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{}

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

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

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{

}

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

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

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

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

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

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

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

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

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

  }
}

通常会用元注解去修饰自定义注解,如上文所示。
例如,使用@Target可以定义Annotation能够被应用于源码的哪些位置:

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

提取注解信息

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

因为注解定义后也是一种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);

使用反射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();

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

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

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

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

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

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

要读取方法参数的注解,我们先用反射获取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;
    }
}

使用注解实例


Demo1

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

Testable.java

import java.lang.annotation.*;

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

如下 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()
	{
	}
}

Demo2

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

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

在某个JavaBean中,使用注解:

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

    @Range(max=10)
    public String city;
}

定义了注解,本身对程序逻辑没有任何影响。必须编写代码来使用注解。这里,编写一个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】:《疯狂Java讲义》
【2】:廖雪峰的官方网站:使用注解
【3】:春晨:@SafeVarargs注解的使用
【4】:廖雪峰的官方网站:自定义注解
【5】:廖雪峰的官方网站:处理注解

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐


@ 注解能被用来为程序元素( 类、 方法、 成员变量等) 设置元数据。 值得指出的是, 注解不影响程序代码的执行, 无论增加、 删除注解, 代码都始终如一地执行。 如果希望让程序中的注解在运行时起一定
@ 1、线性表的概念 线性表是最常见也是最简单的一种数据结构。简言之, 线性表是n个数据元素的有限序列。 其一般描述为: A={a1,a2,……an) 一个数据元素通常包含多个数据项, 此时每个数据元
简介 ArrayList是开发中使用比较多的集合,它不是线程安全的,CopyOnWriteArrayList就是线程安全版本的ArrayList。CopyOnWriteArrayList同样是通过数组
在 Java String类源码阅读笔记 里学习了String类的源码,StringBuilder、StringBuffer是经常拿来和String类做对比的两个类,可谓是“爱恨纠缠” ,这里我们继续
话不多说,先上图。 1、基本概念 欲说线程,必先说进程。 进程:进程是代码在数据集合上的一次运行活动,是系统进行资源分配和调度的基本单位。 线程:线程是进程的一个执行路径,一个进程中至少有一个线程,进
@ 网络基础 计算机网络是指两台或更多的计算机组成的网络,在同一个网络中,任意两台计算机都可以直接通信,因为所有计算机都需要遵循同一种网络协议。 那什么是互联网呢?互联网是网络的网络(internet
JVM是面试中必问的部分,本文通过思维导图以面向面试的角度整理JVM中不可不知的知识。 先上图: 1、JVM基本概念 1.1、JVM是什么 JVM 的全称是 「Java Virtual Machine
@ 本文基于jdk1.8 HashMap采用 key/value 存储结构,每个key对应唯一的value。 在jdk1.7之前,HashMap 的内部存储结构是数组+链表。 在jdk1.8中 Has
@ Eclipse是很多Java开发者的第一个开发工具,尽管开源的Eclipse在一后起之秀的对比下,显得有些颓势,但是,Eclipse有着丰富的插件支持。选择合适的插件,Eclipse表示:老夫也能
@ 准备 LinkedList是基于双向链表数据结构实现的Java集合(jdk1.8以前基于双向循环链表),在阅读源码之前,有必要简单了解一下链表。 先了解一下链表的概念:链表是由一系列非连续的节点组
@ 写博客哪有刷逼乎有意思 1 写博客哪有刷逼乎有意思 2 写博客哪有刷逼乎有意思 3 类的加载、 连接和初始化 系统可能在第一次使用某个类时加载该类, 也可能采用预加载机制来加载某个类。 JVM 和
树结构是一类重要的非线性数据结构。直观来看,树是以分支关系定义的层次结构。树结构在客观世界广泛存在,如人类社会的族谱和各种社会组织机构都可用树来形象表示。 树在计算机领域中也得到广泛应用,尤以二叉树最
@ 本文基于jdk1.8 String类可谓是我们开发中使用最多的一个类了。对于它的了解,仅仅限于API的了解是不够的,必须对它的源码进行一定的学习。 一、前置 String类是Java中非常特别的一
随便打开一个招聘网站,看看对高级Java工程师的技能要求。 抛开其它的经验能力等等,单纯从技术,或者说知识上来讲,可以发现一些共通的地方。 Java基础 计算机基础 数据库,SQL/NoSQL 常用开
@ JDBC指Java 数据库连接,是一种标准Java应用编程接口( JAVA API),用来连接 Java 编程语言和广泛的数据库。 1、JDBC典型用法 1.1、JDBC 4.2 常用接口和类简介
简介 ArrayList是基于数组实现的一种列表。 ArrayList继承体系如下: 图一:ArrayList继承体系 ArrayList实现了List, RandomAccess, Cloneabl
@ Java 的 IO 通过 java.io 包下的类和接口来支持, 在 java.io 包下主要包括输入、 输出两种 10 流, 每种输入、 输出流又可分为字节流和字符流两大类。 其中字节流以字节为
@ 使用断言 断言(Assertion)是一种调试程序的方式。在Java中,使用assert关键字来实现断言。 断言的概念 假设确信某个属性符合要求, 并且代码的执行依赖于这个属性。例如, 需要计算:
@ Java 程序在不同操作系统上运行时,可能需要取得平台相关的属性,或者调用平台命令来完成特定功能。 Java 提供了 System 类和 Runtime 类来与程序的运行平台进行交互。 Syste
@ Java 提供了一个操作 Set 、 List 和 Map等集合的类:Collections , 该工具类里提供了大量方法对集合元素进行排序、 查询和修改等操作,还提供了将集合对象设置为不可变、对