1. 概述
在本文中,我们将讨论Java语言的一个核心特性-JDK中可用的默认注解。
2. 什么是注解
简单地说,注解就是在Java类型前面加上一个“@”符号。
Java从1.5版本开始就有了注解。从那时起,他们塑造了我们设计应用程序的方式。
Spring和Hibernate是非常依赖注解来支持各种设计技术的框架示例。
基本上,注解将额外的元数据分配给它绑定的源代码。通过向方法、接口、类或字段添加注解,我们可以:
- 通知编译器警告和错误
- 在编译时操作源代码
- 在运行时修改或检查行为
3. Java内置注解
现在我们已经回顾了基础知识,让我们看一下核心Java附带的一些注解。首先,有几个通知编译:
- @Override
- @SuppressWarnings
- @Deprecated
- @SafeVarargs
- @FunctionalInterface
- @Native
这些注解生成或抑制编译器警告和错误。始终如一地应用它们通常是一种很好的做法,因为添加它们可以防止未来的程序员错误。
@Override注解用于指示方法覆盖或替换继承方法的行为。
@SuppressWarnings表示我们要忽略部分代码中的某些警告。@SafeVarargs注解还作用于与使用可变参数相关的一种警告。
@Deprecated注解可用于将API标记为不再使用。此外,此注解已在Java 9中进行了改进,以表示有关弃用的更多信息。
对于所有这些,你可以在链接的文章中找到更详细的信息。
3.1 @FunctionalInterface
Java 8允许我们以更函数式的方式编写代码。
单一抽象方法接口是其中的重要组成部分。如果我们打算让lambda使用SAM接口,我们可以选择使用@FunctionalInterface将其标记为这样:
@FunctionalInterface
public interface Adder {
int add(int a, int b);
}
与方法的@Override一样,@FunctionalInterface使用Adder声明我们的意图。
现在,无论我们是否使用@FunctionalInterface,我们仍然可以以相同的方式使用Adder:
Adder adder = (a,b) -> a + b;
int result = adder.add(4,5);
但是,如果我们向Adder添加第二个方法,那么编译器会报错:
@FunctionalInterface
public interface Adder {
// compiler complains that the interface is not a SAM
int add(int a, int b);
int div(int a, int b);
}
现在,这将在没有@FunctionalInterface注解的情况下编译。那么,它给了我们什么?
像@Override一样,这个注解可以保护我们免受未来程序员错误的影响。尽管在一个接口上使用多个方法是合法的,但当该接口被用作lambda目标时就不合法了。如果没有此注解,编译器将在Adder用作lambda的许多地方中断。现在,它只是破坏了Adder本身。
3.2 @Native
从Java 8开始,java.lang.annotation包中有一个名为Native的新注解。@Native注解只适用于字段。它表示带注解的字段是一个常量,可以从本机代码中引用。例如,这是它在Integer类中的使用方式:
public final class Integer {
@Native public static final int MIN_VALUE = 0x80000000;
// omitted
}
这个注解也可以作为工具生成一些辅助头文件的提示。
4. 元注解
接下来,元注解是可以应用于其他注解的注解。
例如,这些元注解用于注解配置:
- @Target
- @Retention
- @Inherited
- @Documented
- @Repeatable
4.1 @Target
注解的范围可以根据要求而变化。虽然一个注解仅用于方法,但另一个注解可以与构造函数和字段声明一起使用。
要确定自定义注解的目标元素,我们需要用@Target注解来标记它。
@Target可以处理12种不同的元素类型。如果我们查看@SafeVarargs的源代码,那么我们可以看到它必须只附加到构造函数或方法:
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.CONSTRUCTOR, ElementType.METHOD})
public @interface SafeVarargs {
}
4.2 @Retention
一些注解旨在用作编译器的提示,而其他注解则在运行时使用。
我们使用@Retention注解来说明我们的注解在我们程序的生命周期中应用的位置。
为此,我们需要使用三种保留策略之一配置@Retention:
- RetentionPolicy.SOURCE:编译器和运行时都不可见
- RetentionPolicy.CLASS:编译器可见
- RetentionPolicy.RUNTIME:编译器和运行时可见
如果注解声明中没有@Retention注解,则保留策略默认为RetentionPolicy.CLASS。
如果我们有一个在运行时应该可以访问的注解:
@Retention(RetentionPolicy.RUNTIME)
@Target(TYPE)
public @interface RetentionAnnotation {
}
那么,如果我们给一个类添加一些注解:
@RetentionAnnotation
@Generated("Available only on source code")
public class AnnotatedClass {
}
现在我们可以反思AnnotatedClass以查看保留了多少注解:
@Test
public void whenAnnotationRetentionPolicyRuntime_shouldAccess() {
AnnotatedClass anAnnotatedClass = new AnnotatedClass();
Annotation[] annotations = anAnnotatedClass.getClass().getAnnotations();
assertThat(annotations.length, is(1));
}
该值为1,因为@RetentionAnnotation具有RUNTIME的保留策略,而@Generated没有。
4.3 @Inherited
在某些情况下,我们可能需要一个子类来将注解绑定到父类。
我们可以使用@Inherited注解使我们的注解从带注解的类传播到它的子类。
如果我们将@Inherited应用于我们的自定义注解,然后将其应用于BaseClass:
@Inherited
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface InheritedAnnotation {
}
@InheritedAnnotation
public class BaseClass {
}
public class DerivedClass extends BaseClass {
}
然后,在扩展BaseClass之后,我们应该看到DerivedClass在运行时似乎具有相同的注解:
@Test
public void whenAnnotationInherited_thenShouldExist() {
DerivedClass derivedClass = new DerivedClass();
InheritedAnnotation annotation = derivedClass.getClass()
.getAnnotation(InheritedAnnotation.class);
assertThat(annotation, instanceOf(InheritedAnnotation.class));
}
如果没有@Inherited注解,上述测试将失败。
4.4 @Documented
默认情况下,Java不会在Javadocs中记录注解的用法。
但是,我们可以使用@Documented注解来改变Java的默认行为。
如果我们创建一个使用@Documented的自定义注解:
@Documented
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface ExcelCell {
int value();
}
并且,将它应用于适当的Java元素:
public class Employee {
@ExcelCell(0)
public String name;
}
然后,Employee Javadoc将显示注解用法:
4.5 @Repeatable
有时在给定的Java元素上多次指定相同的注解可能很有用。
在Java 7之前,我们必须将注解组合成一个容器注解:
@Schedules({
@Schedule(time = "15:05"),
@Schedule(time = "23:00")
})
void scheduledAlarm() {
}
然而,Java 7带来了一种更简洁的方法。使用@Repeatable注解,我们可以使注解可重复:
@Repeatable(Schedules.class)
public @interface Schedule {
String time() default "09:00";
}
要使用@Repeatable,我们也需要有一个容器注解。在这种情况下,我们将重用@Schedules:
public @interface Schedules {
Schedule[] value();
}
当然,这看起来很像我们在Java 7之前所拥有的。但是,现在的价值是当我们需要重复@Schedule时不再指定包装器@Schedules:
@Schedule
@Schedule(time = "15:05")
@Schedule(time = "23:00")
void scheduledAlarm() {
}
因为Java需要包装器注解,所以我们很容易从Java 7之前的注解列表迁移到可重复注解。
5. 总结
在本文中,我们讨论了每个Java开发人员都应该熟悉的Java内置注解。
与往常一样,本教程的完整源代码可在GitHub上获得。