注解的基本概念
Java注解(Annotation)是一种附加在代码中的元数据,用于对程序代码进行说明和补充。同 class 类
和 interface 接口
一样,也属于一种类型。它是在 Java SE 5 中引入的一项新特性,常用于配置、编译和运行时的处理。但是注解并不是所装饰代码的一部分,它对代码的运行效果没有直接影响。
主要的作用有以下四方面:
-
生成文档,通过代码里标识的元数据生成 javadoc 文档。
-
编译检查,通过代码里标识的元数据让编译器在编译期间进行检查验证。
-
编译时动态处理,编译时通过代码里标识的元数据动态处理,例如动态生成代码。
-
运行时动态处理,运行时通过代码里标识的元数据动态处理,例如使用反射注入实例。
注解以 @注解名
格式出现在代码中,可以用于对包、接口、类、方法、变量、参数等进行标注。Java 提供了很多内置注解,如 @Override
、@Deprecated
、@SuppressWarnings
等,也可以自定义注解。
注解的好处是能够在不影响程序执行的情况下提供额外的信息,使得代码更加灵活、可读性更强、易于维护和修改。同时也方便一些工具对代码进行分析和处理,例如 IDE、编译器、代码生成器等。
Java注解的应用场景
Java注解在软件开发中具有广泛的应用场景,包括但不限于:
-
代码生成:在编译时自动生成额外的代码,如Lombok库中的
@Data
、@NoArgsConstructor
等注解,用于自动生成 getter/setter 方法、构造函数等。 -
编译时检查:通过注解在编译时进行格式检查,如
@Override
注解确保方法正确重写了父类中的方法。 -
框架配置:在Spring等框架中,通过注解简化配置,如
@Component
、@Service
、@Repository
等注解用于声明Bean,@Autowired
用于依赖注入。 -
运行时处理:在运行时读取注解信息,实现动态行为,如日志记录、性能监控等。
-
文档生成:通过注解生成 API 文档,如 Javadoc 工具可以读取注解信息并生成文档。
Java 内置注解
Java语言内置了多个注解,这些注解用于提供对程序代码附加的元数据说明,有些用于编译期间的检查,有些用于运行期间的处理。
@Override
@Override
用于表示一个方法覆盖了其超类中的方法。它可以帮助开发人员避免在方法重写时出现错误的签名或拼写错误。
使用 @Override
注解的方法必须满足以下条件:
-
必须是一个实例方法(非
static
和final
方法); -
方法签名与超类中的方法完全相同。
如果满足以上条件,使用 @Override
标注的方法在编译时将会受到更严格的检查。如果方法的签名不正确,编译器将提示一个错误,这有助于避免由于拼写错误或方法签名不匹配导致的编译错误。
@Deprecated
@Deprecated
用于标示某个程序元素(类、方法、字段等)已经过时,不再推荐使用。当开发人员使用被标注为 @Deprecated
的元素时,编译器会发出警告。
使用 @Deprecated
注解的作用是向程序员传达某个元素已经过时,不再是首选的选择,可能存在更好的替代方案。通过标记过时的元素,可以帮助开发人员避免使用不再维护或存在缺陷的代码,以提高代码的质量和可维护性。
虽然 @Deprecated
注解并不会阻止代码的使用,但是作为开发人员,应该尽量遵循注解的意图,考虑使用推荐的替代方案。
@SuppressWarning
@SuppressWarnings
用于抑制编译期间产生的一些警告信息。当某个代码段出现编译器警告,且确定这个警告并不是一个真正的问题时,可以使用 @SuppressWarnings
注解来消除这些警告。
@SuppressWarnings
注解可以应用于很多不同的警告类型,具体的警告类型取决于编译器和开发环境。
例如,常见的警告类型包括未使用变量、弃用方法的使用、类型转换的安全性等。
虽然 @SuppressWarnings
注解可以消除编译器警告,但在使用时应谨慎,确保真正了解警告的原因,并确认忽略这些警告是安全的。避免过度使用 @SuppressWarnings
注解,以免掩盖真正的问题和潜在的错误。
@Suppress(" ") 可取值
参数 | 作用 |
---|---|
all | 抑制所有警告 |
boxing | 抑制装箱、拆箱操作时候的警告 |
cast | 抑制映射相关的警告 |
dep-ann | 抑制启用注释的警告 |
deprecation | 抑制过期方法警告 |
fallthrough | 抑制确在switch中缺失breaks的警告 |
finally | 抑制finally模块没有返回的警告 |
hiding | 抑制与隐藏变数的区域变数相关的警告 |
incomplete-switch | 忽略没有完整的switch语句 |
nls | 忽略非nls格式的字符 |
null | 忽略对null的操作 |
rawtype | 使用generics时忽略没有指定相应的类型 |
restriction | 抑制与使用不建议或禁止参照相关的警告 |
serial | 忽略在serializable类中没有声明serialVersionUID变量 |
static-access | 抑制不正确的静态访问方式警告 |
synthetic-access | 抑制子类没有按最优方法访问内部类的警告 |
unchecked | 抑制没有进行类型检查操作的警告 |
unqualified-field-access | 抑制没有权限访问的域的警告 |
unused | 抑制没被使用过的代码的警告 |
元注解
元注解(Meta-Annotations)是用于注解其他注解的注解。元注解提供了对注解进行更精细的控制和定义。
@Retention
@Retention
用于指定注解的保留策略,即注解可以在何时可用。它可以被用于定义其他注解。
@Retention
元注解有一个 value
属性,用于指定保留策略。可选的保留策略包括:
-
RetentionPolicy.SOURCE
:注解仅在源代码级别可见,在编译之后不会包含在编译后的字节码中。 -
RetentionPolicy.CLASS
:注解在编译时保留,在编译后的字节码中可见,但在运行时不可通过 反射 获取注解信息(默认值)。 -
RetentionPolicy.RUNTIME
:注解在运行时保留,可以通过反射获取注解信息,并根据注解的定义执行相应的代码,对运行时的代码有影响。
自定义注解的保留策略如果不指定,默认为 RetentionPolicy.CLASS
编译器分别使用了 RuntimeInvisibleAnnotations
和 RuntimeVisibleAnnotations
属性去记录了 RetentionPolicy.CLASS
注解的方法 和 @RetentionPolicy.RUNTIME
方法 的注解信息
@Target
@Target
用于指定它所注解的注解类可以应用的目标元素类型。它可以被用于定义其他注解。
@Target
元注解有一个 value
属性,类型为 ElementType[]
,用于指定注解的目标元素类型。可选的目标元素类型包括:
-
ElementType.TYPE
:类、接口、注解、枚举类型 -
ElementType.FIELD
:字段(类的成员变量)、枚举常量 -
ElementType.METHOD
:方法 -
ElementType.PARAMETER
:普通方法或构造方法的参数 -
ElementType.CONSTRUCTOR
:构造方法 -
ElementType.LOCAL_VARIABLE
:局部变量 -
ElementType.ANNOTATION_TYPE
:注解 -
ElementType.PACKAGE
:包 -
ElementType.TYPE_PARAMETER
:泛型参数 -
ElementType.TYPE_USE
:声明语句、泛型或强制转换类型语句中的类型 -
ElementType.MODULE
:模块
多个目标元素类型可以用数组的形式进行指定。
@Documented
@Documented
用于指定被它注解的注解是否应该包含在自动生成的 API 文档(Javadoc)中。它可以被用于定义其他注解。
@Documented
只是一个标记,它本身并不会影响注解的使用和行为。只有在生成 API 文档时才会体现其作用。
通常情况下,如果开发者希望在 API 文档中包含某个注解的信息,就可以为该注解添加 @Documented
元注解。如果不需要在 API 文档中包含注解信息,可以不添加该注解。
@Inherited
@Inherited
用于指定被它注解的 Annotation
是否可以被子类继承。它可以被用于定义其他注解。
如果一个注解被标注了 @Inherited
,并且被应用于一个父类上,那么子类会自动继承该注解,除非子类自己显式声明了相同的注解。
需要注意的是,@Inherited
只会作用于类级别的注解。对于其他类型的元素(如方法、字段等)的注解,无论是否标注了 @Inherited
,子类都不会继承父类的注解。
通常情况下,只有当注解的语义与继承相关时,才需要使用 @Inherited
注解。对于大多数注解来说,不需要使用 @Inherited
注解。
@Repeatable
@Repeatable
,是从 Java 8 开始引入的特性,用于指定某个注解是否可以被多次应用于同一个元素。它可以被用于定义其他注解。
要使用 @Repeatable
,需要满足以下两个条件:
- 定义一个包含重复注解的容器注解(如
MyAnnotations
)。 - 将容器注解的类型指定为
@Repeatable
注解的 value 属性。
使用 @Repeatable
后,可以通过在同一个元素上多次应用注解,简化代码编写。例如,可以在同一个方法上重复应用相同的注解,而不需要将注解放置在一个包含数组的容器注解中。
@Native
@Native
是一个用于标记方法或类的注解,表示该方法或类是由本地代码(如 C、C++ 或其他本地语言)实现的。它是 Java 本地方法接口(Java Native Interface,JNI)的一部分。
@Native
注解通常与 native
关键字一起使用,用于标识需要调用本地代码实现的方法。native
关键字用于声明一个方法是本地方法,而 @Native
注解则为编译器提供额外的信息,指示该方法的实现由本地代码提供。
需要注意的是,使用本地方法时需要使用 JNI
编写对应的本地代码。本地代码通常是使用 C 或 C++ 编写的,通过 JNI 扩展与 Java 代码进行交互。
在 Java 中,本地方法的调用和实现通过 JNI 提供的接口进行连接。Java 虚拟机(JVM)会加载并执行本地代码,从而使 Java 代码能够调用本地方法。
使用本地方法可以实现与底层系统的交互、调用本地库、执行性能敏感的操作等。但需要注意,在使用本地方法时需谨慎处理内存管理和跨平台的兼容性问题。
注解和反射接口
反射包 java.lang.reflect
下的 AnnotatedElement
接口提供这些方法。这里注意:只有注解被定义为 RUNTIME
后,该注解才能是运行时可见,当 class 文件被装载时被保存在 class 文件中的 Annotation
才会被虚拟机读取。
AnnotatedElement
接口是所有程序元素(Class、Method和Constructor)的父接口,所以程序通过反射获取了某个类的AnnotatedElement
对象之后,程序就可以调用该对象的方法来访问Annotation
信息。
自定义注解
@interface
@interface
是 Java 中用于创建自定义注解的关键字。使用 @interface
可以定义一个新的注解类型,并在注解中声明自定义的元素。
下面是 @interface
的语法格式:
public @interface 注解名 {
// 注解元素声明
}
在 @interface
定义的花括号中,可以声明注解的元素。注解元素的定义类似于方法的定义,包括元素的类型、名称和可选的默认值。
一般步骤
自定义注解是使用 Java 的注解机制创建自己的注解。下面是创建自定义注解的一般步骤:
- 使用
@interface
关键字定义一个新的注解类型。 - 声明注解的元素,这些元素就是我们希望在注解中指定的值。
- 使用元注解(如
@Retention
、@Target
等)来配置注解的行为。
下面是一个示例,假设我们要创建一个 Person
注解,用于标识人员信息。该注解包含两个元素:姓名和年龄。
import java.lang.annotation.*;
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface Person {
String name();
int age();
}
在上面的例子中,我们使用 @interface
关键字定义了 Person
注解。注解元素 name
和 age
分别用来表示人员的姓名和年龄。@Retention(RetentionPolicy.RUNTIME)
表示该注解在运行时可用,@Target(ElementType.TYPE)
表示该注解可修饰类和接口。
使用自定义注解时,我们可以像使用其他注解一样将其应用于类或接口,例如:
@Person(name = "Alice", age = 25)
public class MyClass {
// ...
}
在上面的示例中,我们将 @Person
注解应用于 MyClass
类,指定了姓名为 “Alice”,年龄为 25。
需要注意的是,自定义注解的元素可以有默认值,如下所示:
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface Person {
String name() default "Unknown";
int age() default 0;
}
在上面的修改中,我们为 name
和 age
元素指定了默认值,当使用 @Person
注解时,如果不指定这些元素的值,它们将取默认值。
自定义注解可以通过反射技术在运行时获取注解信息,并根据注解信息进行相应的处理。
相关接口
-
boolean isAnnotationPresent(Class<?extends Annotation> annotationClass)
判断该程序元素上是否包含指定类型的注解,存在则返回true,否则返回false。注意:此方法会忽略注解对应的注解容器。
-
<T extends Annotation> T getAnnotation(Class<T> annotationClass)
返回该程序元素上存在的、指定类型的注解,如果该类型注解不存在,则返回null。
-
Annotation[] getAnnotations()
返回该程序元素上存在的所有注解,若没有注解,返回长度为0的数组。
-
<T extends Annotation> T[] getAnnotationsByType(Class<T> annotationClass)
返回该程序元素上存在的、指定类型的注解数组。没有注解对应类型的注解时,返回长度为0的数组。该方法的调用者可以随意修改返回的数组,而不会对其他调用者返回的数组产生任何影响。
getAnnotationsByType
方法与getAnnotation
的区别在于,getAnnotationsByType
会检测注解对应的重复注解容器。若程序元素为类,当前类上找不到注解,且该注解为可继承的,则会去父类上检测对应的注解。
-
<T extends Annotation> T getDeclaredAnnotation(Class<T> annotationClass)
返回直接存在于此元素上的所有注解。与此接口中的其他方法不同,该方法将忽略继承的注释。如果没有注释直接存在于此元素上,则返回null
-
<T extends Annotation> T[] getDeclaredAnnotationsByType(Class<T> annotationClass)
返回直接存在于此元素上的所有注解。与此接口中的其他方法不同,该方法将忽略继承的注释
-
Annotation[] getDeclaredAnnotations()
返回直接存在于此元素上的所有注解及注解对应的重复注解容器。与此接口中的其他方法不同,该方法将忽略继承的注解。
如果没有注释直接存在于此元素上,则返回长度为零的一个数组。该方法的调用者可以随意修改返回的数组,而不会对其他调用者返回的数组产生任何影响。
Java注解的实例
以下是一个简单的自定义注解及其使用的示例:
// 自定义注解
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface MyAnnotation {
String value() default "default value";
}
// 使用自定义注解
public class MyClass {
@MyAnnotation(value = "custom value")
public void myMethod() {
// 方法实现
}
}
// 运行时读取注解信息
public class AnnotationReader {
public static void main(String[] args) {
Method method = MyClass.class.getMethod("myMethod");
if (method.isAnnotationPresent(MyAnnotation.class)) {
MyAnnotation annotation = method.getAnnotation(MyAnnotation.class);
System.out.println(annotation.value()); // 输出:custom value
}
}
}
总结
Java注解是Java语言中一个非常重要的特性,它为Java程序提供了一种灵活的方式来添加元数据。通过注解,开发者可以在不修改代码逻辑的情况下,为代码添加额外的信息,这些信息可以在编译时、加载时或运行时被读取和处理。掌握Java注解的使用,对于提高Java编程的灵活性和可维护性具有重要意义。