JAVA注解详解

Java的注解是JDK1.5之后引入的新特性,用于对代码进行说明,可以对包、类、接口、字段、方法参数、局部变量等进行注解,作用如下:

  1. 编写文档: 通过代码里的标识生成Java Doc文档;
  2. 编译检查: 通过代码里的标识让编译器对代码实现基本的检查;
  3. 代码分析: 通过代码里的标识让编译器对代码进行基本的分析;
  4. 编译时动态处理: 编译时通过代码里的元数据进行动态处理,例如动态生成代码;
  5. 运行时动态处理: 运行时通过代码里的元数据进行动态处理,例如反射注入实例;

为什么要用注解?

想要让静态语言拥有动态特性,有很多种办法,可以使用配置,可以使用代理模式,这两种方式虽然耦合度较低,但是随着项目变大,这类配置会越来越臃肿难以维护,注解是一种高度耦合的配置方式,但它足够简洁,易于修改。

基本内置注解

@Override

在面向对象编程中,经常会继承父类然后重写并覆盖父类的方法,对于重写的方法使用@override方法的作用有以下几个:

  1. 明确标识是重写的方法,便于一起协同的人认识到这是重写的方法
  2. 错误时编译警告,使用@Override,编译器回检查父类与子类方法名,是否相同 出入参是否相同,方法级异常抛出是否相同。

@Deprecated

这个注解是用来标识方法已经过时,在编译时会发出warning的警告,像IDEA这类编译器也会发出警告。

方法过时通常是原作者只是想保留现状,之前的引用保持不变,但并不希望后来者继续使用过时的方法,引入更多的混乱。

@SuppressWarnings

这个注解用于抑制Java的高亮报警,有时候我们通常不想见到这些报警,因为并不是错误,如果注解不带关键字参数,就会自动匹配,但也可以增加关键字参数仅忽略也定告警

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

@SuppressWarnings({ "deprecation", ”unused“ })

@SafeVarargs

在声明具有模糊类型(例如泛型)的可变参数的构造函数或方法时,Java编译器会报unchecked警告。鉴于这种情况,如果开发者断定声明的构造函数和方法的主体不会对其varags参数执行潜在的不安全的操作,可使用@SafeVarargs进行标记,java编译器就不会发出unchecked的警告。

这个注解只能用于标记构造函数和成员方法,对static或final方法都不生效, 在运行时启动。

@FunctionalInterface

标记接口必须是函数式接口,即接口只能包含一个方法。

元注解

元注解负责对其他注解进行说明注释,自定义注解可以使用元注解,元注解可以在java.lang.annotation包中找到。

@Documented

javaDoc可以@Documented注解修饰的注解类或方法形成文档

@Target

用于指定一个注解的修饰范围,即@Target修饰的注解可以被用于什么地方,它包含一个value变量用来设置适用目标。value 是 java.lang.annotation.ElementType 枚举类型的数组。

名称目标
CONSTRUCTOR用于构造方法
FIELD用于成员变量(包含枚举常量)
LOCAL_VARIABLE用于局部变量
METHOD用于方法
PACKAGE用于包
PARAMETER用于类型参数
TYPE用于类、接口(包括注解类型)或 enum 声明
@Target({ElementType.TYpe})

@Retention

用于描述注解的生命周期,以 java.lang.annotation.RetentionPolicy 枚举类型作为其成员变量

名称生命周期
SOURCE在源文件中有效(即源文件保留)
CLASS在CLASS文件中有效(即CLASS保留)
RUNTIME在运行时保留(即运行时有效)

生命周期大小排序为 SOURCE < CLASS < RUNTIME,前者能使用的地方后者一定也能使用。如果需要在运行时去动态获取注解信息,那只能用 RUNTIME 注解;如果要在编译时进行一些预处理操作,比如生成一些辅助代码(如 ButterKnife),就用 CLASS 注解;如果只是做一些检查性的操作,比如 @Override 和 @SuppressWarnings,则可选用 SOURCE 注解。

@Inherited

@Inherited 是一个标记注解,用来指定该注解可以被继承。使用 @Inherited 注解的 Class 类,表示这个注解可以被用于该 Class 类的子类。就是说如果某个类使用了被 @Inherited 修饰的注解,则其子类将自动具有该注解。

@Target({ ElementType.TYPE })
@Inherited
@Retention(RetentionPolicy.RUNTIME)
public @interface MyInherited {
}

@MyInherited
public class TestA {
    public static void main(String[] args) {
        System.out.println(TestA.class.getAnnotation(MyInherited.class));
        System.out.println(TestB.class.getAnnotation(MyInherited.class));
        System.out.println(TestC.class.getAnnotation(MyInherited.class));
    }
}
class TestB extends TestA {
}
class TestC extends TestB {
}

@Repeatable

允许在相同的程序元素中重复相同的注解

public @interface Roles {
    Role[] value();
}
@Repeatable(Roles.class)
public @interface Role {
    String roleName();
}
public class RoleTest {
    @Role(roleName = "role1")
    @Role(roleName = "role2")
    public String doString(){
        ...
    }
}

@Native

使用 @Native 注解修饰成员变量,则表示这个变量可以被本地代码引用,常常被代码生成工具使用。对于 @Native 注解不常使用,了解即可。

自定义注解

方法如下

public @interface Test {
}

定义注解和定义类相似,注解前面的访问修饰符和类一样有两种,分别是公有访问权限(public)和默认访问权限(默认不写)。一个源程序文件中可以声明多个注解,但只能有一个是公有访问权限的注解。且源程序文件命名和公有访问权限的注解名一致。

不包含任何成员变量的注解称为标记注解,例如上面声明的 Test 注解以及基本注解中的 @Override 注解都属于标记注解。根据需要,注解中可以定义成员变量,成员变量以无形参的方法形式来声明,其方法名和返回值定义了该成员变量的名字和类型。代码如下所示:

public @interface MyTag {
    // 定义带两个成员变量的注解
    // 注解中的成员变量以方法的形式来定义
    String name();
    int age();
}

以上代码中声明了一个 MyTag 注解,定义了两个成员变量,分别是 name 和 age。成员变量也可以有访问权限修饰符,但是只能有公有权限和默认权限。

如果在注解里定义了成员变量,那么使用该注解时就应该为它的成员变量指定值,如下代码所示。

public class Test {
    // 使用带成员变量的注解时,需要为成员变量赋值
    @MyTag(name="xx", age=6)
    public void info() {
        ...
    }
    ...
}

注解中的成员变量也可以有默认值,可使用 default 关键字。如下代码定义了 @MyTag 注解,该注解里包含了 name 和 age 两个成员变量。

public @interface MyTag {
    // 定义了两个成员变量的注解
    // 使用default为两个成员变量指定初始值
    String name() default "study";
    int age() default 7;
}

如果为注解的成员变量指定了默认值,那么使用该注解时就可以不为这些成员变量赋值,而是直接使用默认值。

public class Test {
    // 使用带成员变量的注解
    // MyTag注释的成员变量有默认值,所以可以不为它的成员变量赋值
    @MyTag
    public void info() {
        ...
    }
    ...
}

当然也可以在使用 MyTag 注解时为成员变量指定值,如果为 MyTag 的成员变量指定了值,则默认值不会起作用。

根据注解是否包含成员变量,可以分为如下两类。
标记注解:没有定义成员变量的注解类型被称为标记注解。这种注解仅利用自身的存在与否来提供信息,如前面介绍的 @Override、@Test 等都是标记注解。
元数据注解:包含成员变量的注解,因为它们可以接受更多的元数据,所以也被称为元数据注解。

通过反射获取注解信息

获取类、方法、字段的class对象

Class<?> clazz = MyClass.class;                  // 获取类的Class对象
Method method = clazz.getMethod("myMethod");      // 获取方法对象
Field field = clazz.getDeclaredField("myField");  // 获取字段对象

获取注解类型

MyAnnotation annotation = clazz.getAnnotation(MyAnnotation.class);   // 获取类上的注解实例
MyAnnotation annotation1 = method.getAnnotation(MyAnnotation.class);  // 获取方法上的注解实例
MyAnnotation annotation2 = field.getAnnotation(MyAnnotation.class);   // 获取字段上的注解实例

获取注解属性值

String value = annotation.value();    // 获取注解属性值

示例:

Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE, ElementType.METHOD, ElementType.FIELD})
@interface MyAnnotation {
    String value();
}
@MyAnnotation("myClass")
public class MyClass {
    @MyAnnotation("myMethod")
    public void myMethod() {
    }
    @MyAnnotation("myField")
    private String myField;
    public static void main(String[] args) throws Exception {
        Class<?> clazz = MyClass.class;
        // 获取类上的注解
        MyAnnotation annotation = clazz.getAnnotation(MyAnnotation.class);
        String classValue = annotation.value();
        System.out.println("Class value: " + classValue);
        // 获取方法上的注解
        Method method = clazz.getMethod("myMethod");
        MyAnnotation annotation1 = method.getAnnotation(MyAnnotation.class);
        String methodValue = annotation1.value();
        System.out.println("Method value: " + methodValue);
        // 获取字段上的注解
        Field field = clazz.getDeclaredField("myField");
        MyAnnotation annotation2 = field.getAnnotation(MyAnnotation.class);
        String fieldValue = annotation2.value();
        System.out.println("Field value: " + fieldValue);
    }
}

发表回复

您的电子邮箱地址不会被公开。