Java语言高级部分之注解是什么?

一、注解是什么?

        从JDK5开始,Java增加对元数据的支持,也就是注解,注解与注释是有一定区别的,可以把注解理解为代码里的特殊标记,这些标记可以在编译,类加载,运行时被读取,并执行相应的处理。通过注解开发人员可以在不改变原有代码和逻辑的情况下在源代码中嵌入补充信息。

二、JDK中预定义的一些注解

1.@Override

        当子类重写父类方法时,子类可以加上这个注解,那这有什么什么用?这可以确保子类确实重写了父类的方法,避免出现低级错误
        例如:所有父类Object中的toString方法,如果将toString修改为toString01,则它将不是重写父类Object的方法,而是变成AnnotationDemo01类中的方法,此时如果再在toString01方法上加上@Override,则会报错“Method does not override method from its superclass”。

public class AnnotationDemo01 {

    @Override
    public String toString() {
        return super.toString();
    }
}

2.@Deprecated

        这个注解用于表示某个程序元素类、方法等已过时,当其他程序使用已过时的类、方法时编译器会给出警告(删除线,这个见了不少了吧)。
例如:方法show01,由于种种原因已经被淘汰,不建议使用,假若删除,则不能兼容低版本,所以在其方法上加上@Deprecated,告诉使用者,该方法已经被淘汰,不建议继续使用。虽然可以继续使用,但是使用起来会有删除线标致。
在这里插入图片描述

3.@SuppressWarnings

压制警告
        被该注解修饰的元素以及该元素的所有子元素取消显示编译器警告,例如修饰一个类,那他的字段,方法都是显示警告
例如:AnnotationDemo01类中有多个警告,有方法show01的,也有方法demo的,如果想去掉这些警告,可以在各自的方法上添加注解@SuppressWarnings,或者直接在类上加@SuppressWarnings。@SuppressWarnings有个参数value,值为”all“表示所有。
在这里插入图片描述                在这里插入图片描述

4.@SafeVarargs

        抑制“堆污染”警告
想理解这个就要明白什么是堆污染,堆污染是什么?
其实很好理解,就是把不带泛型的对象赋给一个带泛型的对象,为什么不行?很简单,因为不带泛型的话,默认会给泛型设定为object,意思就是什么类型都可以往里面塞,那你一个不带泛型的怎么可能给一个带泛型塞呢。
例如运行如下代码:
List list = new ArrayList(); list.add(20); List ls = list; System.out.println(ls.get(0));则会抛出堆污染异常Exception in thread “main” java.lang.ClassCastException: java.lang.Integer cannot be cast to java.lang.String at Test.Test1.main(Test1.java:29)
注意:可变参数更容易引发堆污染异常,因为java不允许创建泛型数组,可变参数恰恰是数组。
  抑制这个警告的方法有三个:
1.@SafeVarargs修饰引发该警告的方法或构造器
2.使用@suppressWarnings(“unchecked”)
3.编译时使用-Xlint:varargs

5.@FunctionalInterface

什么是函数式?如果接口中只有一个抽象方法(可以包含多个默认方法或多个static方法)
接口体内只能声明常量字段和抽象方法,并且被隐式声明为public,static,final。
接口里面不能有私有的方法或变量。
这个注解有什么用?这个注解保证这个接口只有一个抽象方法,注意这个只能修饰接口

三、自定义注解

1.格式

元注解
public @interface 注解名称{
	属性列表;
}

2.本质

        注解的本质是一个继承java.lang.annotation.Annotation类的接口
        如何获得这个结果:

  1. 在任意目录下创建文件MyAnno.java,内容如下:
    在这里插入图片描述
  2. 编译:MyAnno.java,编译之后会成MyAnno.class文件
    在这里插入图片描述
    在这里插入图片描述
  3. 反编译:MyAnno.class
    在这里插入图片描述

3.属性

        既然注解的本质为接口,接口中的抽象方法即为注解中的属性。但对于属性还存在一些要求:

  1. 属性的返回值类型,只能是以下这五种:基本数据类型,String,枚举,注解,以上类型的数组。
  2. 使用注解时,如果注解中含有属性,则必须给属性赋值。但在定义属性时,使用default关键字给属性赋默认值,则在使用时可以不赋值。
    使用注解时,注解中有多个属性需要赋值,使用逗号隔开。

    在这里插入图片描述            在这里插入图片描述
    使用注解时,注解中有且只有一个属性需要赋值,并且该属性的名称为value,则value可以省略,直接定义值即可。
    在这里插入图片描述            在这里插入图片描述
    使用注解时,注解中的属性返回值为数组类型,值使用{}包裹,如果数组中只有一个值,则可省略{}。
    在这里插入图片描述            在这里插入图片描述

4.元注解(JDK的元Annotation)

        元注解:描述注解的注解

扫描二维码关注公众号,回复: 10155917 查看本文章

4.1.@Target

        描述注解能够作用的位置。比如:@Target(ElementType.METHOD),表示被修饰的注解只能作用于方法之上。

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Target {
    /**
     * Returns an array of the kinds of elements an annotation type
     * can be applied to.
     * @return an array of the kinds of elements an annotation type
     * can be applied to
     */
    ElementType[] value();
}

        ElementType的枚举值:

public enum ElementType {
    /** Class, interface (including annotation type), or enum declaration
    	翻译:类、接口(包括注释类型)或enum声明
    	解释:被该注解修饰的注解只能作用于:类,接口(此前已说过注解的本质为接口,所以此处接口也包括注解)和枚举类型之上*/
    TYPE,

    /** Field declaration (includes enum constants)
    	翻译:字段声明(包括enum常量)
    	解释:被该注解修饰的注解只能作用于:字段声明(字段包括enum常量)之上*/
    FIELD,

    /** Method declaration
    	翻译:方法声明
    	解释:被该注解修饰的注解只能作用于:方法之上 */
    METHOD,

    /** Formal parameter declaration
    	翻译:正式的参数声明
    	解释:被该注解修饰的注解只能作用于:方法参数之上*/
    PARAMETER,

    /** Constructor declaration
    	翻译:构造函数声明
    	解释:被该注解修饰的注解只能作用于:构造函数之上 */
    CONSTRUCTOR,

    /** Local variable declaration
    	翻译:局部变量声明
    	解释:被该注解修饰的注解只能作用于:局部变量之上 */
    LOCAL_VARIABLE,

    /** Annotation type declaration
    	翻译:注释类型声明
    	解释:被该注解修饰的注解只能作用于:注解之上 */
    ANNOTATION_TYPE,

    /** Package declaration
	    翻译:包声明 
	    解释:被该注解修饰的注解只能作用于:包之上*/
    PACKAGE,

    /**
     * Type parameter declaration
     *
     * @since 1.8
	 * 表示该注解能写在类型变量的声明语句中。 java8新增
     */
    TYPE_PARAMETER,

    /**
     * Use of a type
     *
     * @since 1.8
     * 表示该注解能写在使用类型的任何语句中。 java8新增
     */
    TYPE_USE
}

4.2.@Retention

        描述注解被保留的阶段,通过源码可以看出,它只有一个值且为value,则在使用时可以省略“value=”直接赋值;另外RetentionPolicy也是一个枚举类型。

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Retention {
    /**
     * Returns the retention policy.
     * @return the retention policy
     */
    RetentionPolicy value();
}

        RetentionPolicy 的值,通过源码可以看出共有三个,代表被该注解修饰的注解能够被保留到不同的阶段。
        我们知道java在运行时,大致会经历三个阶段:源码阶段,编译阶段和运行时阶段(不知道的朋友可以看一下这里)。
        RetentionPolicy 的值则分别代表这三个阶段,
        @Retention(RetentionPolicy.SOURCE):被该注解修饰的注解只能保留到源码阶段。
        @Retention(RetentionPolicy.CLASS):被该注解修饰的注解只能保留到编译阶段。
        @Retention(RetentionPolicy.RUNTIME):被该注解修饰的注解能够保留到运行时阶段。

public enum RetentionPolicy {
    /**
     * Annotations are to be discarded by the compiler.
     */
    SOURCE,

    /**
     * Annotations are to be recorded in the class file by the compiler
     * but need not be retained by the VM at run time.  This is the default
     * behavior.
     */
    CLASS,

    /**
     * Annotations are to be recorded in the class file by the compiler and
     * retained by the VM at run time, so they may be read reflectively.
     *
     * @see java.lang.reflect.AnnotatedElement
     */
    RUNTIME
}

4.3.@Documented

        描述注解是否被抽取到api文档中
        这个注解用于指定被修饰的注解类将被javadoc工具提取成文档,如果定义注解类时使用了这个注解修饰,则所有使用该注解修饰的程序API文档将会包含该注解说明。

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Documented {
}

4.4.@Inherited

        描述注解是否被子类继承
        这个注解指定被他修饰的注解将具有继承性——如果某个类使用了@Xxx,则其子类将自动被@Xxx修饰

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Inherited {
}

4.5.@Result

作用是在同一个程序元素前使用多个相同类型的注解在java8之前只能通过@Results配置,
java8简化了它的写法例如:
@test(age=5)@test(age=8)public void resultTest(){}

四、在程序中使用(解析)注解

        获取注解中定义的属性的值
案例:

  1. 创建一个注解
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface Pro {
    String className();
    String methodName();
}
  1. 使用@Pro,并解析其属性值
@Pro(className = "cn.zj.domain.Person",methodName = "eat")
public class AnnotationDemo02 {
    public static void main(String[] args) throws Exception {
        //解析注解
        //1.获取该类的字节码文件对象
        Class<AnnotationDemo02> aClass = AnnotationDemo02.class;
        //2.获取上面的注解对象
        /*此步骤实际是在内存中生成了一个该注解接口的子类实现对象*/
        /*public class ProImpl implements Pro{
            public String className(){
                //这里的返回值就是该注解使用时的属性值
                return "cn.zj.domain.Person";
            }

            public String methodName(){
                //这里的返回值就是该注解使用时的属性值
                return "eat";
            }
        }*/
        
        Pro pro = aClass.getAnnotation(Pro.class);
        //3.获取值
        String className = pro.className();
        String methodName = pro.methodName();

        /*==========以上已经获取到注解中的值,以下为反射知识==========*/

        //加载该类进内存
        Class<?> cls = Class.forName(className);

        //使用无参构造函数创建对象
        Object obj = cls.newInstance();

        //获取方法对象
        Method method = cls.getMethod(methodName);

        //执行方法
        method.invoke(obj);

    }
}

五、案例——简单的测试框架

        需求:创建一个注解,只要加上这个注解的方法,运行测试类就能检测出这些加了注解的方法运行时是否会抛异常,并将结果记录在文件中。

  1. 新建一个注解
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Check {
}
  1. 创建一个被测试的方法
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));
    }

    public void show(){
        System.out.println("永无bug……");
    }
}
  1. 创建测试类,只要加上@Check注解的方法都会被测试,并把结果记录在文件中
public class TestCheck {
    public static void main(String[] args) throws IOException, ClassNotFoundException, IllegalAccessException, InstantiationException {

        //1.创建记录异常的文件
        BufferedWriter bw = new BufferedWriter(new FileWriter("bug.txt"));

        //2.获取所有方法
        //2.1.将类加载进内存
        Class<?> cls = Class.forName("cn.zj.annotation.Calculator");
        //2.2.创建对象
        Object obj = cls.newInstance();
        //2.3.获取所有方法
        Method[] declaredMethods = cls.getDeclaredMethods();

        int total=0;//异常发生次数
        for (Method method :
                declaredMethods) {
            //判断方法上是否含有@Check注解
            if(method.isAnnotationPresent(Check.class)){
                //如果方法上有@Check注解,则执行方法
                try {
                    method.invoke(obj);
                } catch (InvocationTargetException e) {
                    //捕获异常记录到文件中
                    total++;
                    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();
                }
            }
        }

        bw.write("本次测试一共出现异常次数:"+total);
        bw.flush();
        bw.close();

    }
}
  1. 结果:
    在这里插入图片描述
发布了24 篇原创文章 · 获赞 7 · 访问量 1916

猜你喜欢

转载自blog.csdn.net/zj499063104/article/details/105031522