首页 > 编程笔记 > Java笔记 阅读:3

Java注解的使用(非常详细)

注解是插入到源代码中的标记,某些特定的工具可以处理这些注解。这些工具可以在源代码级别上进行操作,也可以处理那些被编译器置入注解的类文件。

注解不会改变程序的编译方式。也就是说,Java 编译器对于带有或不带有注解的代码会生成相同的虚拟机指令。

为了更好地利用注解进行编程,需要选择一种注解处理工具,并使用处理工具能够理解的注解,然后才能将该注解处理工具应用于代码。

注解有多种用途。例如,JUnit 可以使用注解来标记执行测试的方法,并指定测试的运行方式。此外,Java 持久性体系结构使用注解来定义类和数据库表之间的映射,这样,开发人员就可以自动持久化对象,而无须编写 SQL 查询语句。

下面是一个简单注解的示例:
public class CacheTest {
   ...
   @Test public void checkRandomInsertions()
}
注解 @Test 用于注解 checkRandomInsertions() 方法。

在 Java 中,注解的用法类似于修饰符(如 public 或 static)。每个注解的名称前面都有一个 @ 符号。

@Test 注解本身不做任何事情。它还需要工具支持才会有用。例如,在测试一个类的时候,JUnit 4 测试工具会调用标记为 @Test 的所有方法。另一个工具可能会从类文件中删除所有测试方法,以便在测试完这个类后,不会将这些测试方法与程序装载在一起。

Java注解元素

注解可以具有称为元素的键/值对,例如:
@Test(timeout=1000)
元素的名称和类型由每个注解定义。元素可以由读取注解的工具处理。

注解元素是以下元素之一:
例如:
@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)。

声明注解可以出现在以下几种声明中:
对于类和接口,需要将注解放在 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>。

类型使用注解可以出现在以下位置:
此外,有一些类型位置无法进行注解:
@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 引用不是给定类型的对象。相反,放置在构造器上的注解描述了构造对象的属性。

相关文章