一、什么是注解
Java注解用于为Java代码提供元数据。作为元数据,注解不直接影响你的代码执行,但也有一些类型的注解可以用于这一目的。Java注解是从Java5开始添加到Java中。可以理解为“给计算机看的注释”。
Java注解通常用于以下目的:
- 编译器指令
- 编译时指令
- 运行时指令
二、Java内置注解
@Override - 检查该方法是否是重写方法。如果发现其父类,或者是引用的接口中并没有该方法时,会报编译错误。
@Deprecated - 标记过时方法。如果使用该方法,会报编译警告。
@SuppressWarnings - 指示编译器去忽略注解中声明的警告。
@SafeVarargs - Java 7 开始支持,忽略任何使用参数为泛型变量的方法或构造函数调用产生的警告。
@FunctionalInterface - Java 8 开始支持,标识一个匿名函数或函数式接口。
三、自定义注解
@interface是声明一个注解的关键字,而这个注解非常相似于接口。注解可有或没有元素。注解元素的特点:
- 没有函数体;
- 没有函数参数;
- 返回的声明必须在一个特定的类型:
- 基本类型 (boolean, int, float,…)
- 枚举
- 注解
- String
- Class(例如String.class)
- 以上类型的数组
- 元素可以有默认值;
例如,下面是一个自定义注解:
@Documented
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface MyFirstAnnotation {
// element name.
public String name();
// Element description, default value "".
public String description() default "";
}
其中,@Documented、@Target、@Retention是元注解。
元注解是什么意思呢?元注解是可以注解到注解上的注解,或者说元注解是一种基本注解,但是它能够应用到其它的注解上面。如果难于理解的话,你可以这样理解。元注解也是一张标签,但是它是一张特殊的标签,它的作用和目的就是给其他普通的标签进行解释说明的。元注解有 @Retention、@Documented、@Target、@Inherited、@Repeatable 5 种。
1、元注解
(1)@Retention
Retention 的英文意为保留期的意思。当 @Retention 应用到一个注解上的时候,它解释说明了这个注解的的存活时间。
它的取值如下:
- RetentionPolicy.SOURCE 注解只在源码阶段保留,在编译器进行编译时它将被丢弃忽视。
- RetentionPolicy.CLASS 注解只被保留到编译进行的时候,它并不会被加载到 JVM 中。
- RetentionPolicy.RUNTIME 注解可以保留到程序运行的时候,它会被加载进入到 JVM 中,所以在程序运行时可以获取到它们。这是最常用的。
我们可以这样的方式来加深理解,@Retention 去给一张标签解释的时候,它指定了这张标签张贴的时间。@Retention 相当于给一张标签上面盖了一张时间戳,时间戳指明了标签张贴的时间周期。
(2)@Documented
这个元注解和文档有关。它的作用是能够将注解中的元素包含到 Javadoc 中去。
(3)@Target
Target 是目标的意思,@Target 指定了注解运用的地方。
你可以这样理解,当一个注解被 @Target 注解时,这个注解就被限定了运用的场景。 @Target 有下面的取值:
- ElementType.ANNOTATION_TYPE 可以给一个注解进行注解
- ElementType.CONSTRUCTOR 可以给构造方法进行注解
- ElementType.FIELD 可以给属性进行注解
- ElementType.LOCAL_VARIABLE 可以给局部变量进行注解
- ElementType.METHOD 可以给方法进行注解
- ElementType.PACKAGE 可以给一个包进行注解
- ElementType.PARAMETER 可以给一个方法内的参数进行注解
- ElementType.TYPE 可以给一个类型进行注解,比如类、接口、枚举
(4)@Inherited
Inherited 是继承的意思,但是它并不是说注解本身可以继承,而是说如果一个超类被 @Inherited 注解过的注解进行注解的话,那么如果它的子类没有被任何注解应用的话,那么这个子类就继承了超类的注解。
说的比较抽象。代码来解释。
@Inherited
@Retention(RetentionPolicy.RUNTIME)
@interface Test {}
@Test
public class A {}
public class B extends A {}
注解 Test 被 @Inherited 修饰,之后类 A 被 Test 注解,类 B 继承 A,类 B 也拥有 Test 这个注解。
(5)@Repeatable
Repeatable 自然是可重复的意思。@Repeatable 是 Java 1.8 才加进来的,所以算是一个新的特性。
当我们需要重复使用某个注解时,希望利用相同的注解来表现所有的形式时,我们可以借助@Repeatable注解。
比如,@MyAnnotation被@Repeatable注解,可以这样用:
@MyAnnotation(...)
@MyAnnotation(...)
@MyAnnotation(...)
public class MyClass{
...
}
2、注解的属性
注解的属性也叫做成员变量。注解只有成员变量,没有方法。注解的成员变量在注解的定义中以“无形参的方法”形式来声明,其方法名定义了该成员变量的名字,其返回值定义了该成员变量的类型。
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface TestAnnotation {
int id();
String msg();
}
上面代码定义了 TestAnnotation 这个注解中拥有 id 和 msg 两个属性。在使用的时候,我们应该给它们进行赋值。
赋值的方式是在注解的括号内以 value=”” 形式,多个属性之前用 “,”隔开。
@TestAnnotation(id=3,msg="hello annotation")
public class Test {
}
需要注意的是,在注解中定义属性时它的类型必须是 8 种基本数据类型外加String类、Class类、接口、注解及它们的数组。
注解中属性可以有默认值,默认值需要用 default 关键值指定。比如:
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface TestAnnotation {
public int id() default -1;
public String msg() default "Hi";
}
TestAnnotation 中 id 属性默认值为 -1,msg 属性默认值为 Hi。
它可以这样应用。
@TestAnnotation()
public class Test {}
因为有默认值,所以无需要再在 @TestAnnotation 后面的括号里面进行赋值了,这一步可以省略。
另外,还有一种情况。如果一个注解内仅仅只有一个名字为 value 的属性时,应用这个注解时可以直接接属性值填写到括号内。
public @interface Check {
String value();
}
上面代码中,Check 这个注解只有 value 这个属性。所以可以这样应用。
@Check("hi")
int a;
这和下面的效果是一样的
@Check(value="hi")
int a;
最后,还需要注意的一种情况是一个注解没有任何属性。比如
public @interface Perform {}
那么在应用这个注解的时候,括号都可以省略。
@Perform
public void testMethod(){}
四、具体案例
利用注解写一个简单的测试框架,当主方法执行后,会自动执行被检测的所有方法(被Check注解的方法),判断方法是否有异常,记录到文件中。
1、@Check类
package Study.Annotation_Study;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Check {
}
2、TestCheck类
package Study.Annotation_Study;
import java.io.BufferedWriter;
import java.io.FileWriter;
import java.io.IOException;
import java.lang.reflect.Method;
/**
* @author NJUPT_MR.Z
* @create 2022-05-16 20:11
* @Description 简单的测试框架
* 当主方法执行后,会自动执行被检测的所有方法(被Check注解的方法),判断方法是否有异常,记录到文件中
*/
public class TestCheck {
public static void main(String[] args) throws IOException {
//创建计算器对象
Calculator c = new Calculator();
//获取字节码文件对象
Class cls = c.getClass();
//获取所有方法
Method[] methods = cls.getDeclaredMethods();
int number = 0;//出现异常的次数
BufferedWriter bw = new BufferedWriter(new FileWriter("src\\Study\\Annotation_Study\\bug.txt"));
for (Method method : methods) {
//判断方法上是否有Check注解
if (method.isAnnotationPresent(Check.class)) {//有,执行
try {
method.invoke(c);
} catch (Exception e) {//捕获异常,记录到文件中
bw.write(method.getName() + "方法出异常了");
bw.newLine();
bw.write("异常的名称是:" + e.getCause().getClass().getSimpleName());
bw.newLine();
bw.write("异常的原因是+" + e.getCause().getMessage());
bw.newLine();
bw.write("-------------");
bw.newLine();
number++;
}
}
}
bw.write("本次测试一共出现 " + number + " 次异常");
bw.flush();
bw.close();
}
}
3、利用上述两个类测试Calculator类
package Study.Annotation_Study;
/**
* @author NJUPT_MR.Z
* @create 2022-05-16 20:08
* @Description 计算器类
*/
public class Calculator {
@Check
public void add() {
System.out.println("1 + 0 = " + (1 + 0));
}
@Check
public void sub() {
System.out.println("1 - 0 = " + (1 - 0));
}
@Check
public void mul() {
System.out.println("1 * 0 = " + (1 * 0));
}
@Check
public void div() {
System.out.println("1 / 0 = " + (1 / 0));
}
@Check
public void show(){
String s = null;
s.toString();
}
}
运行TestCheck类的main方法,生成bug.txt文件,内容如下:
div方法出异常了
异常的名称是:ArithmeticException
异常的原因是+/ by zero
-------------
show方法出异常了
异常的名称是:NullPointerException
异常的原因是+Cannot invoke "String.toString()" because "s" is null
-------------
本次测试一共出现 2 次异常