Java 注解
一. 什么是注解
注解,可以理解为标签,是一种特殊的“注释”,用来标识或解释 Java 代码,是给机器看的(而注释是给程序员看的)。
注解的定义:注解也叫元数据,跟类、接口、枚举是同一个层次的,也是 java 的一种类型,在 Java SE 5.0 开始引入,放在 Java 源码的类、方法、字段、参数前面,用来进行注释或说明。
二. 注解的作用
注解的作用有以下三类:
- 编写文档:通过代码里标识的注解生成文档(生成文档 doc 文档)
- 代码分析:通过代码里标识的注解对代码进行分析(使用反射)
- 编译检查:通过代码里标识的注解让编译器能够实现基本的编译检查(Override 等)
三. 注解的分类
-
标准注解
标准注解是指 JDK 自带的几个注解,主要有这几个:
- @Override:检测该注解标注的方法是否是继承自父类(接口)的
- @Deprecated:该注解标注的内容,标识已过时
- @SuppressWarnings:压制警告,一般传递参数 all,
@SuppressWarnings
- @FunctionalInterface:函数式接口注解
-
元注解
元注解是用来修饰其他注解的,主要有以下几种:
-
@Retention:解释说明了注解的生命周期,有以下三种取值
RetentionPolicy.SOURCE
:注解只在源码阶段保留,在编译器进行编译时将注解丢弃或忽视RetentionPolicy.CLASS
:注解只保留到编译进行时,不会被加载进 JVMRetentionPolicy.RUNTIME
:注解保留到程序运行时,会被加载进入 JVM 中,所以在程序运行时可以获取到它们
@Retention(RetentionPolicy.RUNTIME) public @interface MyAnnotation { int value(); }
如果定义注解时
@Retention
不存在,则该 Retention 默认为RetentionPolicy.CLASS,但我们通常自定义的注解都是 RUNTIME 时使用,所以必须要加上@Retention(RetentionPolicy.RUNTIME
-
@Documented:用来将注解中的元素包含到 Javadoc 文档中
-
@Target:定义注解所修饰的对象范围,有以下 7 种取值
ElementType.PACKAE
:可以给一个包进行注解ElementType.TYPE
:可以给一个类型进行注解,比如类,接口,枚举等ElementType.CONSTRUCTOR
:可以给构造方法进行注解ElementType.METHOD
:可以给方法进行注解ElementType.PARAMETER
:可以给一个方法内的参数进行注解ElementType.FIELD
:可以给属性进行注解ElementType.LOCAL_VARIABLE
:可以给局部变量进行注解
@Target(ElementType.METHOD) // 限制了注解 MyAnnotation 只能用于解释说明某个方法 public @interface MyAnnotation { int value(); }
-
@Inherited:定义子类是否可以继承父类定义的注解,如果一个 class 使用了 @Inherited 修饰的注解,那么这个注解将被用于这个 class 的子类。
- 这个元注解只对类的继承起效,对接口的继承无效。
- 适用前提是:子类没有被任何注解应用。
// 元注解 @Inherited 作用于 注解 MyAnnotation @Inherited public @interface MyAnnotation { int value(); } // 注解 MyAnnotation 作用于 class A @MyAnnotation public class A {
}
// class B 继承了 class A, 而且 class B 没有被其他注解应用
// 则 class B 继承了 class A 的注解 @MyAnnotation
public class B extends A {}
-
-
自定义注解
-
注解的定义格式:通过@interface关键字自定义
public @interface MyAnnotation {
}
// 这样就定义了一个注解了注解本质上就是一个接口,该接口默认继承 Annotation 接口
public interface MyAnnotation extends java.lang.annotation.Annotation {}
-
注解的属性
注解的属性其实就是注解接口中的抽象方法,只是叫做属性,不叫方法
public @interface MyAnnotation { int id(); String name(); }// 这个注解有两个属性:id 和 name
注解的属性是用无参的方法的形式来声明的,方法名就是属性名,比如上面的 id 和 name,方法返回值就是属性的类型(下面介绍有多少种类型)
-
注解属性的类型
属性的类型(方法的返回值) 有以下几种取值:
- 基本数据类型(int,double 之类)
- String
- 枚举
- 注解
- 以上类型的数组
-
注解属性的赋值
属性赋值的格式是:注解 (属性名 1=value1, 属性名 2=value2...),用逗号分隔。
在定义了属性之后,在使用注解的属性时需要赋值,有以下三种情况:
- 定义属性时,可以使用default关键字给属性设置默认值,那么在使用属性时可以不赋值。
- 如果只有一个属性需要赋值,并且属性名字为 value,则可以省略 value,直接赋值。
- 数组赋值时,值用花括号 {} 包裹,如果数组只有一个值,则花括号可以省略。
public @interface MyAnnotation { int id() default 0; String name(); }// 这个注解有两个属性:id 和 name
// 使用注解
@MyAnnotation(id=1,name="zhangsan")
public class A {
}-
获取注解
获取某个对象上的所有注解,需要使用反射技术。
注意:反射的时间成本比较高,所以注解的使用需要慎重。
主要步骤有 3 个:
- 获取注解定义的那个对象 (类,方法,成员变量, 对应为 Class,Method,Field)
- 获取指定的注解:
<T extends Annotation> getAnnotation(Class<T> annotationClass)
:返回指定类型的注解,如果不存在返回 nullAnnotation[] getDeclaredAnnotations()
: 返回该元素上的所有注解
- 调用注解中的抽象方法获取配置的属性值
还有一个 API,这个方法在是 Class 类对象的成员方法,用于某个类是否应用了某个注解:
public boolean isAnnotationPresent(Class<? extends Annotation> annotationClass)
-
四. 注解实例
-
注解的获取
import java.lang.annotation.*;
/**
- 定义一个注解
注解在程序运行时使用需要加上@Retention(RetentionPolicy.RUNTIME)
*/
public MyAnnotation {
// 定义两个属性
int id() default -1;
String type();
}
-
简单的测试小框架:测试类中的方法是否有问题
执行加了注解的方法,如果有 bug,就输出到 log 文件中。
import java.lang.annotation.*;
/**
- 定义一个注解,用于方法上
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface Check {
}
public class TestClass { @Check public void add(int a, int b) { System.out.println("a + b =" + (a + b)); } @Check public void sub(int a, int b) { System.out.println("a - b =" + (a - b)); } @Check public void div(int a, int b) { System.out.println("a / b =" + (a / b)); } }
import java.io.*; import java.lang.reflect.*; public class CheckClass { public static void main(String[] args) throws IOException { // 创建 TestClass 对象 TestClass tc = new TestClass(); // 获取 Class 对象 Class<?> cls = tc.getClass(); // 获取所有的方法 Method[] methods = cls.getMethods(); // 出错的次数 int errorNum = 0; // log 文件 BufferedWriter bw = new BufferedWriter(new FileWriter("log.txt")); // 遍历所有的方法,有注解 Check 的方法执行 for (Method m : methods) { // 判断是否有 Check 注解 if (m.isAnnotationPresent(Check.class)) { // 执行方法 try { m.invoke(tc, 1, 0); } catch (Exception e) { // 将异常信息写入 log 文件 errorNum++; bw.write(m.getName() + "出现异常了"); bw.newLine(); bw.write("异常类型为:" + e.getCause().getClass().getName()); bw.newLine(); bw.write("异常原因为:" + e.getCause().getMessage()); bw.newLine(); bw.write("------------------------------------"); bw.newLine(); } } } bw.write("本次测试一共出现了" + errorNum + "次异常"); bw.newLine(); bw.flush(); bw.close(); } }
最后运行结果为:
$ java CheckClass a + b = 1 a - b = 1
打开 log.txt 文件如下:
div出现异常了 异常类型为:java.lang.ArithmeticException 异常原因为:/ by zero ------------------------------------ 本次测试一共出现了1次异常
- 定义一个注解,用于方法上
import java.lang.reflect.*;
/**
* 定义一个被注解 MyAnnotation 应用的类
*/
@MyAnnotation(id=1,type="Class")
public class ClassA {
// 注解作用于成员
@MyAnnotation(id=2,type="Field")
public int field;
// 注解作用于方法
@MyAnnotation(id=3,type="Method")
public void method() {
}
public static void main(String[] args) throws NoSuchFieldException, SecurityException, NoSuchMethodException {
// 1. 获取类上的注解
// 先判断类是不是应用了该注解
boolean flag = ClassA.class.isAnnotationPresent(MyAnnotation.class);
// 如果应用了,获取他的属性
if(flag) {
MyAnnotation annotation = ClassA.class.getAnnotation(MyAnnotation.class);
int id = annotation.id();
String type = annotation.type();
System.out.println("ClassA id:" + id);
System.out.println("ClassA type:" + type);
// 输出:
// ClassA id: 1
// ClassA type: Class
}
// 2. 获取成员变量上的注解
// 先利用反射获取成员变量 field
Field f = ClassA.class.getField("field");
flag = f.isAnnotationPresent(MyAnnotation.class);
if (flag) {
MyAnnotation annotation1 = f.getAnnotation(MyAnnotation.class);
System.out.println("Field id:" + annotation1.id());
System.out.println("Field type:" + annotation1.type());
// 输出:
// Field id: 2
// Field type: Field
}
// 3. 获取方法上的注解
Method m = ClassA.class.getMethod("method");
flag = f.isAnnotationPresent(MyAnnotation.class);
if (flag) {
MyAnnotation annotation2 = m.getAnnotation(MyAnnotation.class);
System.out.println("Method id:" + annotation2.id());
System.out.println("Method type:" + annotation2.type());
// 输出:
// Method id: 3
// Method type: Method
}
}
}