JMing's Blog

Java中的注解

字数统计: 2.5k阅读时长: 9 min
2024/04/09

部分资料来源:http://c.biancheng.net。

一、注解的概念

从 jdk 1.5 之后可以在源代码中嵌入一些补充信息,这种补充信息称为注解(Annotation)。注解并不能改变程序的运行结果,也不会影响程序运行的性能。有些注解可以在编译时给用户提示或警告,有的注解可以在运行时读写字节码文件信息。

二、注解的作用

  1. 生成帮助文档。这是最常见的,也是 Java 最早提供的注解。常用的有 @see@param@return 等;
  2. 跟踪代码依赖性,实现替代配置文件功能。比较常见的是 Spring 2.5 开始的基于注解配置。作用就是减少配置。现在的框架基本都使用了这种配置来减少配置文件的数量;
  3. 在编译时进行格式检查。如把 @Override 注解放在方法前,如果这个方法并不是重写了父类方法,则编译时就能检查出。

三、常用的注解

简介

在 jdk 1.8 中提供了 11 种内部注解,其中有 5 个基本注解、6 个元注解。

基本注解包括:@Override、@Deprecated、@SuppressWarnings、@SafeVarargs 和 @FunctionalInterface。

使用

@Override 注解

作用目标:成员方法

作用:对方法进行对父类方法重写的检查,保证重写的一定是父类原有的方法。(重写就是子类重新定义覆盖父类原有的方法,方法签名保持一致,但其内容被改变。)

例:如下代码中是一个 Person 类中对 toString 方法的覆盖重写(toString 方法是 Object 类中的方法),如果方法签名与父类不一致,则编译会报错。

1
2
3
4
5
6
7
8
/* Person.java */
@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}

@Deprecated 注解

作用目标:构造方法、成员变量、局部变量、方法、参数、类等

作用:用于将某个元素标记为过时的元素,使用过时的元素在编译时会给出警告。(在现代 IDE 中,如 IntelliJ IDEA,使用过时的元素会有删除线的效果)

@SuppressWarnings 注解

作用目标:构造方法、成员变量、局部变量、方法、参数、类等

作用:消除警告

关键字 用途
all 抑制所有警告
boxing 抑制装箱、拆箱操作时候的警告
cast 抑制映射相关的警告
dep-ann 抑制启用注释的警告
deprecation 抑制过期方法警告
fallthrough 抑制在 switch 中缺失 breaks 的警告
finally 抑制 finally 模块没有返回的警告
hiding 抑制相对于隐藏变量的局部变量的警告
incomplete-switch 忽略不完整的 switch 语句
nls 忽略非 nls 格式的字符
null 忽略对 null 的操作
rawtypes 使用 generics 时忽略没有指定相应的类型
restriction 抑制禁止使用劝阻或禁止引用的警告
serial 忽略在 serializable 类中没有声明 serialVersionUID 变量
static-access 抑制不正确的静态访问方式警告
synthetic-access 抑制子类没有按最优方法访问内部类的警告
unchecked 抑制没有进行类型检查操作的警告
unqualified-field-access 抑制没有权限访问的域的警告
unused 抑制没被使用过的代码的警告

例:消除使用过期方法的警告

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
/* Student.java */
public class Student extends Person {

// 将成员方法 study() 标记为已弃用方法
@Deprecated
public void study() {
System.out.println("学习");
}
}

/* Main.java */
// 将 @SuppressWarnings 注解作用在 main() 方法,使得在 main() 方法中使用已弃用方法不会得到警告
@SuppressWarnings("deprecation")
public static void main(String[] args) {
Student student = new Student();
student.study();
}

@SafeVarargs 注解

作用目标:构造方法、方法(不适用于非 static 或 非 final 方法,请改用 @SuppressWarnings 注解)

作用:消除来自形参化 vararg 类型的可能的堆污染警告

堆污染是一个技术术语。它指的是引用的类型不是它们指向的对象的父类型。所以可能会引发异常

例:消除可能存在堆污染的警告

1
2
3
4
5
6
7
@SafeVarargs
public static <T> void test(T... args) {
for (T arg : args) {
// 用到了一点点反射,作用是拿到 arg 的类对象,然后拿到类名。
System.out.println(arg.getClass().getName() + ": " + arg);
}
}

@FunctionalInterface 注解

作用目标:接口

作用:检查并保证该接口为函数式接口(接口中仅包含一个抽象方法,如果没有或有多个抽象方法,编译器会报错)

1
2
3
4
5
6
7
8
9
10
11
12
13
/* IFunctionalTest.java */
// 定义一个没啥用(仅用来测试)的函数式接口
@FunctionalInterface
public interface IFunctionalTest {
void test();
}

/* Main.java */
// 用 lambda 表达式来实现接口中的抽象方法
public static void main(String[] args) {
IFunctionalTest test = () -> System.out.println("test");
test.test(); // "test"
}

四、元注解与自定义注解

简介

元注解

在 jdk 1.8 中提供了6 个元注解(元注解就是注解的注解,定义一个新注解需要用到元注解)

包括:@Documented、@Target、@Retention、@Inherited、@Repeatable 和 @Native。其中 @Repeatable 和 @Native 注解是 jdk 1.8 新增。

自定义注解

格式:

1
2
3
4
5
6
7
8
9
10
11
/* 定义 */
访问修饰 @interface 注解名 {
访问修饰 类型名 成员变量名() default 默认值;
...
}

/* 使用 */
public class Test {
@注解名(成员变量名1 = 值1, 成员变量名2 = 值2...)
各种元素;
}

注意:

  1. 访问修饰符只能是 public 和 (default)(表示不写、默认,并非是关键字 default),无论是注解还是注解的成员变量。
  2. 成员变量名后要加一对小括号。
  3. 注解的成员变量可以有默认值,通过 default关键字指定默认值。
  4. 如果注解只有一个成员变量并且名字为 value,那么则可以直接写成 @注解名(值) 的形式。

使用

@Documented

在默认情况下,Java doc 不会提取注解信息,但是如果声明注解时使用了 @Documented 注解,那么该注解就能够被 Java doc 提取到。

@Target

@Target 注解用来指定注解的作用目标,也就是该注解能够用在哪些目标下。目标有以下几种:

名称 说明
CONSTRUCTOR 用于构造方法
FIELD 用于成员变量(包括枚举常量)
LOCAL_VARIABLE 用于局部变量
METHOD 用于方法
PACKAGE 用于包
PARAMETER 用于类型参数(JDK 1.8新增)
TYPE 用于类、接口(包括注解类型)或 enum 声明

上述值是枚举类型 ElementType 的值,存在于 java.lang.annotation 包下。

1
2
3
4
5
6
7
8
9
10
11
/* MyAnnotation.java */
@Target(ElementType.FIELD)
public @interface MyAnnotation {
String value();
}

/* Test.java */
public class Test {
@MyAnnotation("number")
private int number;
}

以上代码是 @Target 注解的使用示例,@MyAnnotation 注解适用于成员变量,如果放在其他元素上会导致编译不通过。

@Retention

@Retention 注解用于指定注解的生命周期,生命周期有以下 3 种:

名称 说明
SOURCE 在资源文件中有效,注解将被编译器丢弃
CLASS 会被编译器记录在 .class 文件中,但在运行时不会被 VM 保留
RUNTIME 在运行时有效,运行时会被 VM 保留并可能被反射获取到

上述值是枚举类型 RetentionPolicy 的值,同样存在于 java.lang.annotation 包下。

@Repeatable

@Repeatable 可以使同一个注解在同一个目标上重复使用,但是需要一个容器注解配合。

在没有 @Repeatable 注解时在同一个目标上重复使用同一个注解:要套壳。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
/* MyAnnotation.java */
@Target({ElementType.FIELD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface MyAnnotation {
String value();
}

/* MyAnnotations.java */
@Target({ElementType.TYPE, ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
public @interface MyAnnotations {
MyAnnotation[] value();
}

/* Test.java */
public class Test {
@MyAnnotations({
@MyAnnotation("repeat"),
@MyAnnotation("repeat")
})
private int number;
}

使用 @Repeatable 注解:可在同一目标直接上重复使用注解。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
/* MyAnnotation.java */
@Target({ElementType.FIELD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Repeatable(MyAnnotations.class)
public @interface MyAnnotation {
String value();
}


/* Test.java */
public class Test {
@MyAnnotation("repeat")
@MyAnnotation("repeat")
private int number;
}

注意:无论有没有 @Repeatable 注解都需要一个容器注解用于存放注解,使用了 @Repeatable 注解只是在重复使用同一注解时省略了套壳而已。

@Inherited

正常情况下,子类继承父类时,不会将作用在父类上的注解也继承过来。

@Inherited 注解的作用就是让子类继承父类的时候将注解也一并继承过来。

请看以下代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
/* MyAnnotation.java */
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface MyAnnotation {
String value();
}

/* Person.java */
@MyAnnotation("ttt")
public class Person {}

/* Student.java */
public class Student extends Person {}

/* Test.java */
Annotation[] annotations = Student.class.getAnnotations();
for (Annotation a : annotations) {
System.out.println(a);
}

// 运行没有打印任何结果

以上代码中定义了一个 @MyAnnotation 注解,Person 类和 Student 类,注解作用在Person 类上 Student 类继承自 Person 类。我们在测试代码中浅浅的使用了 Java 的反射特性来获取作用在类上的注解,但是没有任何结果,说明作用在其上的注解并没有被继承过来。

我们为 @MyAnnotation 注解加上 @Inherited 试试:

1
2
3
4
5
6
7
8
9
10
11
/* MyAnnotation.java */
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Inherited
public @interface MyAnnotation {
String value();
}

/* Test.java */
// ...
// 运行结果:@com.jm.MyAnnotation(value="ttt")

可以看到有结果了,打印出来了完整注解名和其中成员变量的值,说明该注解有被子类继承过来。

反射在下一篇章,也有些书籍或教程上会先讲反射再讲注解。

@Native

@Native 注解只适用于字段。它指示带注释的字段是可以从本机代码引用的常量。例:它在 Integer 类中是如何使用的:

1
2
3
public final class Integer {
@Native public static final int MIN_VALUE = 0x80000000;
}

该注解还可以作为工具生成一些辅助头文件的提示。

@Native 注解这段解释搬运自

https://www.baeldung.com/java-default-annotations

由于本人能力有限,实在不知道该注解的作用以及如何使用,所以就直接把该网站上对其的解释搬运过来了

CATALOG
  1. 1. 一、注解的概念
  2. 2. 二、注解的作用
  3. 3. 三、常用的注解
    1. 3.1. 简介
    2. 3.2. 使用
      1. 3.2.1. @Override 注解
      2. 3.2.2. @Deprecated 注解
      3. 3.2.3. @SuppressWarnings 注解
      4. 3.2.4. @SafeVarargs 注解
      5. 3.2.5. @FunctionalInterface 注解
  4. 4. 四、元注解与自定义注解
    1. 4.1. 简介
      1. 4.1.1. 元注解
      2. 4.1.2. 自定义注解
    2. 4.2. 使用
      1. 4.2.1. @Documented
      2. 4.2.2. @Target
      3. 4.2.3. @Retention
      4. 4.2.4. @Repeatable
      5. 4.2.5. @Inherited
      6. 4.2.6. @Native