JAVA注解详解
Java的注解是JDK1.5之后引入的新特性,用于对代码进行说明,可以对包、类、接口、字段、方法参数、局部变量等进行注解,作用如下:
- 编写文档: 通过代码里的标识生成Java Doc文档;
- 编译检查: 通过代码里的标识让编译器对代码实现基本的检查;
- 代码分析: 通过代码里的标识让编译器对代码进行基本的分析;
- 编译时动态处理: 编译时通过代码里的元数据进行动态处理,例如动态生成代码;
- 运行时动态处理: 运行时通过代码里的元数据进行动态处理,例如反射注入实例;
为什么要用注解?
想要让静态语言拥有动态特性,有很多种办法,可以使用配置,可以使用代理模式,这两种方式虽然耦合度较低,但是随着项目变大,这类配置会越来越臃肿难以维护,注解是一种高度耦合的配置方式,但它足够简洁,易于修改。
基本内置注解
@Override
在面向对象编程中,经常会继承父类然后重写并覆盖父类的方法,对于重写的方法使用@override方法的作用有以下几个:
- 明确标识是重写的方法,便于一起协同的人认识到这是重写的方法
- 错误时编译警告,使用@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);
}
}
发表回复