Java注解的使用(非常详细)
注解是插入到源代码中的标记,某些特定的工具可以处理这些注解。这些工具可以在源代码级别上进行操作,也可以处理那些被编译器置入注解的类文件。
注解不会改变程序的编译方式。也就是说,Java 编译器对于带有或不带有注解的代码会生成相同的虚拟机指令。
为了更好地利用注解进行编程,需要选择一种注解处理工具,并使用处理工具能够理解的注解,然后才能将该注解处理工具应用于代码。
注解有多种用途。例如,JUnit 可以使用注解来标记执行测试的方法,并指定测试的运行方式。此外,Java 持久性体系结构使用注解来定义类和数据库表之间的映射,这样,开发人员就可以自动持久化对象,而无须编写 SQL 查询语句。
下面是一个简单注解的示例:
在 Java 中,注解的用法类似于修饰符(如 public 或 static)。每个注解的名称前面都有一个 @ 符号。
@Test 注解本身不做任何事情。它还需要工具支持才会有用。例如,在测试一个类的时候,JUnit 4 测试工具会调用标记为 @Test 的所有方法。另一个工具可能会从类文件中删除所有测试方法,以便在测试完这个类后,不会将这些测试方法与程序装载在一起。
注解元素是以下元素之一:
例如:
注解元素可以具有默认值。例如,JUnit @Test 注解的 timeout 元素具有默认值 0L。因此,注解 @Test 等价于 @Test(timeout=0L)。
如果元素名称是 value,并且这是你指定的唯一元素,则可以省略 value=。例如,@SuppressWarnings("unchecked") 与 @SuppressWarnings(value="unchecked") 相同。
如果元素值是数组,那么可以将其组件括在花括号中:
如果数组只有一个组件,则可以省略花括号:
注解元素可以是另一个注解:
如果注解的作者声明它是可重复的,则可以将同一注解重复多次:
声明注解可以出现在以下几种声明中:
对于类和接口,需要将注解放在 class 或 interface 关键字和任何修饰符之前:
对于变量,需要将它们放在类型之前:
泛型类或方法中的类型参数可以像下面这样注解:
注意,@NonNull 注解是 Checker Framework 的一部分。通过使用该框架可以在程序中包含断言,例如参数为非 null 或 String 包含正则表达式。此外,静态分析工具会检查断言在给定的源代码中是否有效。
现在假设我们有一个 List<String> 类型的参数,并且想要表达所有字符串都是非null的。这就是类型使用注解的主要用处,可以将注解放在类型参数之前:List<@NonNull String>。
类型使用注解可以出现在以下位置:
此外,有一些类型位置无法进行注解:
可以在其他修饰符(例如 private 和 static)之前或之后放置注解。通常(但不是必须)将类型使用注解放在其他修饰符之后,并将声明注解放在其他修饰符之前。例如:
注意,注解的作者需要指定特定注解可以出现的位置。如果变量和类型使用都允许使用注解,并且在变量声明中使用了注解,则变量和类型使用都会被注解,示例如下。
但是 p 呢?调用方法时,接收器变量 this 绑定到 p,但 this 从未被声明过,因此无法对其进行注解。
实际上,可以使用一个很少使用的语法变体来声明它,这样就可以添加注解了:
注意,你只能为方法提供接收器参数,而不能为构造器提供接收器参数。从概念上讲,在构造器完成之前,构造器中的 this 引用不是给定类型的对象。相反,放置在构造器上的注解描述了构造对象的属性。
注解不会改变程序的编译方式。也就是说,Java 编译器对于带有或不带有注解的代码会生成相同的虚拟机指令。
为了更好地利用注解进行编程,需要选择一种注解处理工具,并使用处理工具能够理解的注解,然后才能将该注解处理工具应用于代码。
注解有多种用途。例如,JUnit 可以使用注解来标记执行测试的方法,并指定测试的运行方式。此外,Java 持久性体系结构使用注解来定义类和数据库表之间的映射,这样,开发人员就可以自动持久化对象,而无须编写 SQL 查询语句。
下面是一个简单注解的示例:
public class CacheTest { ... @Test public void checkRandomInsertions() }注解 @Test 用于注解 checkRandomInsertions() 方法。
在 Java 中,注解的用法类似于修饰符(如 public 或 static)。每个注解的名称前面都有一个 @ 符号。
@Test 注解本身不做任何事情。它还需要工具支持才会有用。例如,在测试一个类的时候,JUnit 4 测试工具会调用标记为 @Test 的所有方法。另一个工具可能会从类文件中删除所有测试方法,以便在测试完这个类后,不会将这些测试方法与程序装载在一起。
Java注解元素
注解可以具有称为元素的键/值对,例如:@Test(timeout=1000)元素的名称和类型由每个注解定义。元素可以由读取注解的工具处理。
注解元素是以下元素之一:
- 基本类型值;
- String;
- Class对象;
- enum的实例;
- 注解;
- 以上类型的数组(但不是数组的数组)。
例如:
@BugReport(showStopper=true, assignedTo="Harry", testCase=CacheTest.class, status=BugReport.Status.CONFIRMED)注解元素的值永远不能为 null。
注解元素可以具有默认值。例如,JUnit @Test 注解的 timeout 元素具有默认值 0L。因此,注解 @Test 等价于 @Test(timeout=0L)。
如果元素名称是 value,并且这是你指定的唯一元素,则可以省略 value=。例如,@SuppressWarnings("unchecked") 与 @SuppressWarnings(value="unchecked") 相同。
如果元素值是数组,那么可以将其组件括在花括号中:
@BugReport(reportedBy={"Harry", "Fred"})
如果数组只有一个组件,则可以省略花括号:
@BugReport(reportedBy="Harry") // Same as {"Harry"}
注解元素可以是另一个注解:
@BugReport(ref=@Reference(id=11235811), ...)注意,由于注解是由编译器处理的,因此所有元素值都必须是编译时常量。
Java多重注解和重复注解
一个项目可以有多个注解:@Test @BugReport(showStopper=true, reportedBy="Joe") public void checkRandomInsertions()
如果注解的作者声明它是可重复的,则可以将同一注解重复多次:
@BugReport(showStopper=true, reportedBy="Joe") @BugReport(reportedBy={"Harry", "Carl"}) public void checkRandomInsertions()
Java注解声明
到目前为止,你已经看到了应用于方法声明的注解。还有许多其他地方可以出现注解。它们分为两类:声明(declaration)和类型使用(type use)。声明注解可以出现在以下几种声明中:
- 类(包括 enum)和接口(包括注解接口);
- 方法;
- 构造器;
- 实例变量(包括 enum 常量);
- 局部变量(包括在 for 和带资源的 try 语句中声明的变量);
- 参数变量和 catch 子句参数;
- 类型参数;
- 包。
对于类和接口,需要将注解放在 class 或 interface 关键字和任何修饰符之前:
@Entity public class User { ... }
对于变量,需要将它们放在类型之前:
@SuppressWarnings("unchecked") List<User> users = ...; public User getUser(@Param("id") String userId)
泛型类或方法中的类型参数可以像下面这样注解:
public class Cache<@Immutable V> { ... }包是在文件 package-info.java 中注解的,该文件只包含前面带有注解的包语句:
Package-level Javadoc需要注意的是,注解的 import 语句在 package 声明之后。
注意,编译类时,局部变量和包的注解将被丢弃。因此,只能在源码级别处理它们。
Java 类型使用注解
声明注解提供了正在被声明项的相关信息。例如,在下面的声明中:public User getuser(@NonNull String userId)它断言 userId 参数不为 null。
注意,@NonNull 注解是 Checker Framework 的一部分。通过使用该框架可以在程序中包含断言,例如参数为非 null 或 String 包含正则表达式。此外,静态分析工具会检查断言在给定的源代码中是否有效。
现在假设我们有一个 List<String> 类型的参数,并且想要表达所有字符串都是非null的。这就是类型使用注解的主要用处,可以将注解放在类型参数之前:List<@NonNull String>。
类型使用注解可以出现在以下位置:
- 与泛型类型参数一起使用:List<@NonNull String>、Comparator.<@NonNull String> reverseOrder()。
- 数组中的任何位置:@NonNull String[][] words (words[i][j] 为非 null)、String @NonNull [][] words ( words为非null)、String[] @NonNull [ ] words ( words[i] 为非 null)。
- 与超类和实现接口一起使用:class Warning extends @Localized Message。
- 与构造器调用一起使用:new @Localized String(...)。
- 与嵌套类型一起使用:Map.@Localized Entry。
- 与强制转换和 instanceof 检查一起使用:(@Localized String) text,if (text instanceof @Localized String)。(注解仅供外部工具使用。它们对强制转换或 instanceof 检查没有影响。)
- 与异常规范一起使用:public String read() throws @Localized IOException。
- 与通配符和类型限定一起使用:List<@Localized ? extends Message >, List<? extends @Localized Message>。
- 与方法和构造器引用一起使用:@Localized Message::getText。
此外,有一些类型位置无法进行注解:
@NonNull String.class // Error—cannot annotate class literal import java.lang.@NonNull String; // Error—cannot annotate import
可以在其他修饰符(例如 private 和 static)之前或之后放置注解。通常(但不是必须)将类型使用注解放在其他修饰符之后,并将声明注解放在其他修饰符之前。例如:
private @NonNull String text; // Annotates the type use @Id private String userId; // Annotates the variable
注意,注解的作者需要指定特定注解可以出现的位置。如果变量和类型使用都允许使用注解,并且在变量声明中使用了注解,则变量和类型使用都会被注解,示例如下。
public User getUser(@NonNull String userId)如果 @NonNull 可以同时应用于参数和类型使用,则会注解 userId 参数,并且参数类型为 @NonNull String。
Java使接收器显式
假设想要将参数注解为在方法中不会被修改:public class Point { public boolean equals(@ReadOnly Object other) { ... } }那么,处理此注解的工具将在看到调用 p.equals(q) 时,推断出 q 没有被改变。
但是 p 呢?调用方法时,接收器变量 this 绑定到 p,但 this 从未被声明过,因此无法对其进行注解。
实际上,可以使用一个很少使用的语法变体来声明它,这样就可以添加注解了:
public class Point { public boolean equals(@ReadOnly Point this, @ReadOnly Object other) { ... } }第一个参数称为接收器参数,它必须命名为 this,它的类型是正在构造的类。
注意,你只能为方法提供接收器参数,而不能为构造器提供接收器参数。从概念上讲,在构造器完成之前,构造器中的 this 引用不是给定类型的对象。相反,放置在构造器上的注解描述了构造对象的属性。