Java 中的注解
注解概述
注解(Annotation),也叫元数据。一种代码级别的说明。它是 JDK1.5 及以后版本引入的一个特性,与类、接口、枚举是在同一个层次。它可以声明在包、类、字段、方法、局部变量、方法参数等的前面,用来对这些元素进行说明,注释。
简单来说,就是说明程序的。给计算机看的。这里要说一下注释:用文字描述程序的。给程序员看的。
作用分类
- 编写文档:通过代码里标识的注解生成文档【生成文档 doc 文档】
- 代码分析:通过代码里标识的注解对代码进行分析【使用反射】
- 编译检查:通过代码里标识的注解让编译器能够实现基本的编译检查【Override】
JDK 中预定义的一些注解
@Override
检测被该注解标注的方法是否是继承自父类 (接口) 的
上例中,该类继承了父类 Object 类,重写了 toString()方法,用 @Override 注解进行标示。而 toString1() 方法不是重写其继承的父类、超类或接口中的方法,用 @Override 注解进行标示,会出现编译错误(Method does not override method from its superclass)。
@Deprecated
该注解标注的内容,表示已过时
创建了 method1 方法,不过后来发现有更好的方式,可以实现 method1 方法的功能,便创建了 method2 方法。可是为了 method1 方法还能使用,便用 @Deprecated 注解进行标示,标示该方法已经过时了,如果我们继续调用 method1 方法,如上图所示,方法中出现一条横线。
@SuppressWarnings
压制警告
表示在注释元素(以及注释元素中包含的所有程序元素)中应该抑制命名的编译器警告。 请注意,给定元素中抑制的一组警告是所有包含元素中抑制的警告的超集。 例如,如果您注释一个类来抑制一个警告并注释方法来抑制另一个警告,则两个警告将在该方法中被抑制。一般传递参数 all —— @SuppressWarnings("all")
自定义注解:格式和本质
格式
复制元注解 public @interface 注解名称 { 属性列表; }
本质
定义一个注解:
public @interface MyAnnotation { /* 属性:接口中的抽象方法 */ public abstract String method(); }
注解本质上就是一个接口,MyAnnotation 接口默认继承 Annotation 接口。接口中可以定义的成员方法是接口的属性。
复制public interface MyAnnotation extends java.lang.annotation.Annotation {}
public interface Annotation 接口,是所有注释类型扩展的公共接口。
自定义注解:属性定义
属性
接口中的抽象方法。
要求
-
属性的返回值类型有下列取值
-
基本数据类型
-
String
-
枚举
复制
public enum Person { P1, P2 } -
注解
复制
public @interface MyAnnotation2 { } -
以上数据类型的数组
使用的时候:
-
-
定义了属性,在使用时需要给属性赋值
如:定义一个注解
在使用的时候:-
如果定义属性时,使用 default 关键字给属性默认初始化值,则使用注解时,可以不进行属性的赋值。
如:定义一个注解
在使用的时候:
1、使用默认值:"LeeHua"
2、不使用默认值: -
如果只有一个属性需要赋值,并且属性的名称是 value,则 value 可以省略,直接定义值即可。
如:定义一个注解
使用的时候:可以省略 value,也可以不省略 value,如下 -
数组赋值时,值使用 {} 包裹。如果数组中只有一个值,则 {} 可以省略
如:定义一个注解
使用的时候,{} 可以省略,也可以不省略:
-
元注解
简单来说,元注解就是描述注解的注解。如 @Override
这里的 @Target 和 @Retention 就是元注解。
四个元注解
@Target // 描述注解能够作用的位置 @Retention // 描述注解被保留的阶段 @Documented // 描述注解是否被抽取到 api 文档中 @Inherited // 描述注解是否被子类继承
@Target
复制public @interface Target { /** * 返回注释类型可应用于的元素种类的数组。 */ ElementType[] value(); }
ElementType[] 是一个枚举数组:
/ * 这个枚举类型的常量提供了在 Java 程序中可能出现注释的句法位置的简单分类。 * 在 java.lang.annotation.Target 元注释中使用这些常量来指定写入给定类型的 * 注释的合法位置。 / public enum ElementType { /* 类,接口(包括注释类型)或枚举声明 / TYPE, /* 字段声明(包括枚举常数) / FIELD, /* 方法声明 / METHOD, /* 形式参数声明 / PARAMETER, /* 构造函数声明 / CONSTRUCTOR, /* 局部变量声明 / LOCAL_VARIABLE, /* 注释类型声明 / ANNOTATION_TYPE, /* 包声明 */ PACKAGE, / * 类型参数声明 * * @since 1.8 */ TYPE_PARAMETER, /** * 使用类型 * * @since 1.8 */ TYPE_USE }
举例
自定义一个注解:
import java.lang.annotation.ElementType; import java.lang.annotation.Target; @Target(ElementType.TYPE) // 表示 MyAnnotation7 注解只能作用于类上 public @interface MyAnnotation7 { }
该注解的使用:
在这里,MyAnnotation7 注解作用在类上是可以的。不过,作用在方法上,或成员变量上、方法上、... ... ,是不可以的。
假如想要作用于成员变量上、方法上、... ... ,可以根据 public enum ElementType {... ...} 在调用元注解的注解上进行添参,从而实现更多的作用域,如:
import java.lang.annotation.ElementType; @Target(ElementType.TYPE, , ElementType.METHOD, ElementType.FIELD) // 表示 MyAnnotation7 注解能作用于类、方法、成员变量上 public @interface MyAnnotation7 { }
@Retention
复制@Documented @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.ANNOTATION_TYPE) public @interface Retention { /** * @return 保留策略。 */ RetentionPolicy value(); }
RetentionPolicy[] 是一个枚举数组:
/ * 注释保留策略。 此枚举类型的常量描述了用于保留注释的各种策略。 它们 * 与@Retention元注释类型一起使用,以指定将保留注释多长时间。 / public enum RetentionPolicy { /* 注释将被编译器丢弃。 / SOURCE, /* 注释将由编译器记录在类文件中,但 JVM 不需要在运行时保留。 */ CLASS, / * 注释将由编译器记录在类文件中,并由 JVM 在运行时保留,因此可以反射读取。 * @see java.lang.reflect.AnnotatedElement */ RUNTIME }
举例
import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @Retention(RetentionPolicy.RUNTIME) // 当前被描述的注解(MyAnnotation8),会保留到 class 字节码文件中,并被 JVM 读取到 public @interface MyAnnotation8 { }
@Documented
复制/** * 表示具有类型的注释默认情况下由 javadoc 和类似工具记录。 * 即:描述注解是否被抽取到 api 文档中 */ @Documented @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.ANNOTATION_TYPE) public @interface Documented { }
举例
自定义一个注解:
import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; import java.lang.annotation.ElementType; import java.lang.annotation.Documented; @Target({ElementType.TYPE, ElementType.METHOD, ElementType.FIELD}) // 表示 MyAnnotation7 注解能作用于类、方法、成员变量上 @Retention(RetentionPolicy.RUNTIME) // 当前被描述的注解(MyAnnotation8),会保留到 class 字节码文件中,并被 JVM 读取到 @Documented // 描述注解会被抽取到 api 文档中 public @interface MyAnnotation9 { }
使用该注解:
@MyAnnotation9 public class Demo09Annotation { private String name = "LeeHua"; @MyAnnotation9 public void method() { } }
在终端,进行编译:
复制cd /Users/liyihua/IdeaProjects/Study/src/view/study/demo47/ javadoc Demo09Annotation.java
javadoc 编译完成后,生成一大堆文件:
Google 浏览器打开 index-all.html 文件,可以查看被抽取到 API 文档中的描述:
@Inherited
复制/** 描述注解是否被子类继承 */ @Documented @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.ANNOTATION_TYPE) public @interface Inherited { }
举例
自定义一个注解:
import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; import java.lang.annotation.ElementType; import java.lang.annotation.Documented; import java.lang.annotation.Inherited; @Target({ElementType.TYPE, ElementType.METHOD, ElementType.FIELD}) // 表示 MyAnnotation7 注解能作用于类、方法、成员变量上 @Retention(RetentionPolicy.RUNTIME) // 当前被描述的注解(MyAnnotation8),会保留到 class 字节码文件中,并被 JVM 读取到 @Documented // 描述注解会被抽取到 api 文档中 @Inherited // 描述注解是否被子类继承 public @interface MyAnnotation10 { }
父类使用这个注解:
@MyAnnotation10 public class Demo09Annotation { }
子类继承父类:
public class Demo10Annotation extends Demo09Annotation { }
在 @Inherited 注解描述的情况下,父类使用了 @MyAnnotation10 注解,子类继承父类,也会使用 @MyAnnotation10 注解。
案例一
需求
写一个 "框架",不能改变该类的任何代码的前提下,可以帮我们创建任意类的对象,并且执行其中任意方法
实现之前,定义 Person.java、Student.java:
package view.study.demo48; public class Person { private String name; private int age; public Person() { } public Person(String name, int age) { this.name = name; this.age = age; } @Override public String toString() { return "Person{" + "name='" + name + ''' + ", age=" + age + '}'; } public void personMethod() { System.out.println("我是 Person 中的方法!!!"); } public String getName() { return name; } public void setName(String name) { this.name = name; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } } package view.study.demo48; public class Student { private String name; private int age; public Student() { } public Student(String name, int age) { this.name = name; this.age = age; } @Override public String toString() { return "Person{" + "name='" + name + ''' + ", age=" + age + '}'; } public void studentMethod() { System.out.println("我是 Student 中的方法!!!"); } public String getName() { return name; } public void setName(String name) { this.name = name; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } }
实现
定义一个注解:
package view.study.demo48; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; @Target(ElementType.TYPE) // 注解能作用于类上 @Retention(RetentionPolicy.RUNTIME) // 当前被描述的注解,会保留到 class 字节码文件中,并被 JVM 读取到 public @interface pro { public abstract String className(); public abstract String methodName(); }
定义实现类:
package view.study.demo48; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; /** * 写一个 "框架",不能改变该类的任何代码的前提下,可以帮我们创建任意类的对象,并且执行其中任意方法 * 利用:反射、注解实现 */ @pro(className = "view.study.demo48.Person", methodName = "personMethod") public class Demo01Reflection { public static void main(String[] args) throws ClassNotFoundException, IllegalAccessException, InstantiationException, NoSuchMethodException, InvocationTargetException { // 1. 解析注解 // 1.1 获取 Demo01Reflection 类的字节码文件对象 Class<Demo01Reflection> drClass = Demo01Reflection.class; // 2. 获取上边的注解对象:@pro(className = "view.study.demo48.Person", methodName = "personMethod") pro annotation = drClass.getAnnotation(pro.class); // 3. 调用注解中定义的抽象方法,获取返回值 // 3.1 获取 className String className = annotation.className(); // 3.2 获取 methodName String methodName = annotation.methodName(); // 4. 加载该类(获取到的类)进内存 Class<?> aClass = Class.forName(className); // 5. 创建对象 Object object = aClass.newInstance(); // 6. 获取方法对象 Method method = aClass.getMethod(methodName); // 7. 执行方法 method.invoke(object); } }
第二步:
// 第二步其实就是在内存中生成了一个该注解接口的子类实现对象 public class proImpl implements pro { // 实现 pro 接口中的 className()方法和 methodName() 方法 public String className() { return className; } public String methodName() { return methodName; } }
运行程序,控制台输出:
复制我是 Person 中的方法!!!
修改注解中的 className、methodName:
复制@pro(className = "view.study.demo48.Student", methodName = "studentMethod")
再次运行程序,控制台输出:
复制我是 Student 中的方法!!!
案例二
需求
利用反射、注解,写一个简单的测试框架,当主方法执行后,会自动加载被检测的所有方法,判断是否有异常,并记录到文件中。注解名称是 @Check
被测试的方法如下:
package view.study.demo48; public class Calculator { /** 加法 / @Check public int add(int a, int b) { return a + b; } /* 减法 / @Check public int sub(int a, int b) { return a - b; } /* 乘法 / @Check public int mul(int a, int b) { return a * b; } /* 除法 */ @Check public int div(int a, int b) { return a / b; } public void method() { System.out.println("永无 bug!!!"); } }
实现
自定义一个注解:
package view.study.demo48; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; @Retention(RetentionPolicy.RUNTIME) // 当前被描述的注解,会保留到 class 字节码文件中,并被 JVM 读取到 @Target(ElementType.METHOD) // 注解能作用于方法 public @interface Check { }
主方法实现类:
package view.study.demo48; import java.io.IOException; import java.io.FileWriter; import java.io.BufferedWriter; import java.lang.reflect.Method; public class Demo01Calculator { public static void main(String[] args) throws IOException { // 1. 创建计算器对象 Calculator calculator = new Calculator(); // 2. 通过计算器对象,获取字节码文件 Class<? extends Calculator> aClass = calculator.getClass(); // 3. 通过字节码文件,获取 calculator 中所有的方法 Method[] methods = aClass.getDeclaredMethods(); // 4. 定义异常出现的次数、创建字符缓冲输入流 int count = 0; BufferedWriter bufferedWriter = new BufferedWriter(new FileWriter("bug.txt", true)); // 5. 判断方法上是否有 "@Check" 注解 for (Method method : methods) { if (method.isAnnotationPresent(Check.class)) { try { // 6. 如果方法上有 "@Check" 注解,那么执行该方法 method.invoke(calculator, 1, 0); } catch (Exception e) { // 7. 捕获异常,记录到文件中 count ++; bufferedWriter.newLine(); bufferedWriter.write("被测试的方法:" + method.getName() + ",该方法出现异常。"); bufferedWriter.newLine(); bufferedWriter.write("异常的名称:" + e.getCause().getClass().getSimpleName()); bufferedWriter.newLine(); bufferedWriter.write("异常的原因:" + e.getCause().getMessage()); bufferedWriter.newLine(); } } } bufferedWriter.write("本次测试一共出现了" + count + "次异常。"); bufferedWriter.newLine(); bufferedWriter.write("===================================="); // 8. 释放 BufferedWriter 资源 bufferedWriter.flush(); bufferedWriter.close(); } }
运行主方法,bug.txt 文件中会写入一定的内容,内容如下:
复制被测试的方法:div,该方法出现异常。 异常的名称:ArithmeticException 异常的原因:/ by zero 本次测试一共出现了 1 次异常。 ====================================
分析:
要被检测的方法有四个,分别是:加法、减法、乘法、除法。
主方法中传入的参数 a、b 是:1 和 0
很明显,1 除 0,是无意义的,所以 div 方法出现异常,异常名称是:ArithmeticException
其他方法没有出现异常。所以出现的异常次数是:1