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) */ @Retention(RetentionPolicy.RUNTIME) public @interface MyAnnotation { // 定义两个属性 int id() default -1; String type(); }
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 } } }
-
简单的测试小框架:测试类中的方法是否有问题
执行加了注解的方法,如果有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次异常