Java 中的注解

注解概述

注解(Annotation),也叫元数据。一种代码级别的说明。它是JDK1.5及以后版本引入的一个特性,与类、接口、枚举是在同一个层次。它可以声明在包、类、字段、方法、局部变量、方法参数等的前面,用来对这些元素进行说明,注释。

简单来说,就是说明程序的。给计算机看的。这里要说一下注释:用文字描述程序的。给程序员看的。

作用分类

  1. 编写文档:通过代码里标识的注解生成文档【生成文档doc文档】
  2. 代码分析:通过代码里标识的注解对代码进行分析【使用反射】
  3. 编译检查:通过代码里标识的注解让编译器能够实现基本的编译检查【Override】

JDK中预定义的一些注解

@Override

检测被该注解标注的方法是否是继承自父类(接口)的

img

上例中,该类继承了父类Object类,重写了toString()方法,用@Override注解进行标示。而toString1()方法不是重写其继承的父类、超类或接口中的方法,用@Override注解进行标示,会出现编译错误(Method does not override method from its superclass)。

@Deprecated

该注解标注的内容,表示已过时

img

创建了method1方法,不过后来发现有更好的方式,可以实现method1方法的功能,便创建了method2方法。可是为了method1方法还能使用,便用@Deprecated注解进行标示,标示该方法已经过时了,如果我们继续调用method1方法,如上图所示,方法中出现一条横线。

@SuppressWarnings

压制警告

img

表示在注释元素(以及注释元素中包含的所有程序元素)中应该抑制命名的编译器警告。 请注意,给定元素中抑制的一组警告是所有包含元素中抑制的警告的超集。 例如,如果您注释一个类来抑制一个警告并注释方法来抑制另一个警告,则两个警告将在该方法中被抑制。一般传递参数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 接口,是所有注释类型扩展的公共接口。

自定义注解:属性定义

属性

接口中的抽象方法。

要求

  1. 属性的返回值类型有下列取值

    • 基本数据类型

    • String

    • 枚举

      public enum Person {
          P1, P2
      }
      
    • 注解

      public @interface MyAnnotation2 {
      }
      
    • 以上数据类型的数组
      img
      使用的时候:
      img

  2. 定义了属性,在使用时需要给属性赋值
    如:定义一个注解
    img
    在使用的时候:
    img

    • 如果定义属性时,使用default关键字给属性默认初始化值,则使用注解时,可以不进行属性的赋值。
      如:定义一个注解
      img
      在使用的时候:
      1、使用默认值:"LeeHua"
      img
      2、不使用默认值:
      img

    • 如果只有一个属性需要赋值,并且属性的名称是value,则value可以省略,直接定义值即可。
      如:定义一个注解
      img
      使用的时候:可以省略value,也可以不省略value,如下
      img

      img

    • 数组赋值时,值使用{}包裹。如果数组中只有一个值,则{}可以省略
      如:定义一个注解
      img
      使用的时候,{}可以省略,也可以不省略:
      img

      img

元注解

简单来说,元注解就是描述注解的注解。如@Override

img

这里的@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 {

}

该注解的使用:

img

在这里,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编译完成后,生成一大堆文件:
img

img

img

img

Google浏览器打开index-all.html文件,可以查看被抽取到API文档中的描述:

img

@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

posted @ 2020-02-14 03:36  LeeHua  阅读(644)  评论(0编辑  收藏  举报