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
本文来自博客园,作者:LeeHua,转载请注明原文链接:https://www.cnblogs.com/liyihua/p/12305369.html