java注解--史上最清晰,讲解最全!!!



前言

在学习注解前,先来认识一下注释,我们都知道注释是是用来说明代码的,不会经过编译,注释的主要目的是方便程序员阅读!
同样,注解也是用来说明代码的,只不过注解是方便计算机阅读;

声明:文章篇幅可能有点长,希望耐心阅读,相信定有收获!!

一、注解是什么及其本质?

(1)注解的本质

简单来说,注解是JDK1.5之后引入的新特性,用来标注程序的,提供与程序有关的数据,但与程序本省无关,可以作用在包,类,方法,字段等前面,使用注解时用@+注解名称

注解的本质是接口
我们可以用反编译来验证

public @interface TestAnno {

}

在这里插入图片描述

先编译为字节码文件,再用javap反编译可以知道:

注解的本质是一个interface接口,并且继承了Annotation接口

通过查看Annotation的继承关系进一步验证
在这里插入图片描述
可得知Annotation接口是所有注解的父接口

(2)注解的分类

JDK内置注解
Java 定义了一套注解,共有 7 个,3 个在 java.lang 中,剩下 4 个在 java.lang.annotation 中

java.lang包中的三个注解:
- @Override - 检查该方法是否是重写方法。如果发现其父类或者是引用的接口中并没有该方法时,会报编译错误。
- @Deprecated - 标记过时方法。如果使用该方法,会报编译警告。
- @SuppressWarnings - 指示编译器去忽略注解中声明的警告

java.Annotation中的4个注解,也叫元注解
所谓元注解:是用来描述注解的注解,简单来说,元注解规定了其他注解作用的地方,时间等;
- @Target:表示该注解作用的地方,是在方法上,还是成员变量上,还是类上
- @Rentation:表示该注解保留的阶段,是在源代码阶段,还是class类对象阶段,还是runtime运行时阶段
- @Documented:表示该注解是否可以被抽取到文档中
- @Inherited:表示该注解是是否可以被继承

@Target注解:

在这里插入图片描述
返回值类型是枚举类型

public enum ElementType {
    TYPE,               /* 类、接口(包括注释类型)或枚举声明  */

    FIELD,              /* 字段声明(包括枚举常量)  */

    METHOD,             /* 方法声明  */

    PARAMETER,          /* 参数声明  */

    CONSTRUCTOR,        /* 构造方法声明  */

    LOCAL_VARIABLE,     /* 局部变量声明  */

    ANNOTATION_TYPE,    /* 注释类型声明  */

    PACKAGE             /* 包声明  */
}

一般使用一下3个较多:
TYPE:表示被@Target元注解标注的注解只能作用在类上,如果作用在其他地方会报错
FIELD:表示被@Target元注解标注的注解只能作用在成员变量上,如果作用在其他地方会报错
METHOD:表示被@Target元注解标注的注解只能作用在方法上,如果作用在其他地方会报错

@Rentetion注解:

同样@Rentetion中的RentetionPolicy属性也是枚举类型在这里插入图片描述

public enum RetentionPolicy {
    SOURCE,            /* Annotation信息仅存在于编译器处理期间,编译器处理完之后就没有该Annotation信息了  */

    CLASS,             /* 编译器将Annotation存储于类对应的.class文件中。默认行为  */

    RUNTIME            /* 编译器将Annotation存储于class文件中,并且可由JVM读入 */
}

注意:RetentionPolicy的取值一般是RUNTIME

@Documented注解:

类和方法的 注解 在缺省情况下是不出现在 javadoc 中的。如果使用 @Documented 修饰该 注解,则表示它可以出现在 javadoc 中。
定义 注解时,@Documented 可有可无;若没有定义,则 注解不会出现在 javadoc 中。
比如这里我们自定义了MyAnno注解并且使用了@Documented元注解:
在这里插入图片描述
然后在类中使用该注解:
在这里插入图片描述
用javadoc命令生成文档,可以发现此时多出了@MyAnno注解
在这里插入图片描述

@Inherited注解:

如果一个类使用了被该元注解标注的注解,那么这个类的子类同样会拥有这个注解

import java.lang.annotation.Target;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Inherited;

/**
 * 自定义的Annotation。
 */
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@interface Inheritable
{
}

@Inheritable
class InheritableFather
{
    public InheritableFather() {
        // InheritableBase是否具有 Inheritable Annotation
        System.out.println("InheritableFather:"+InheritableFather.class.isAnnotationPresent(Inheritable.class));
    }
}

/**
 * InheritableSon 类只是继承于 InheritableFather,
 */
public class InheritableSon extends InheritableFather
{
    public InheritableSon() {
        super();    // 调用父类的构造函数
        // InheritableSon类是否具有 Inheritable Annotation
        System.out.println("InheritableSon:"+InheritableSon.class.isAnnotationPresent(Inheritable.class));
    }
   
    public static void main(String[] args)
    {
        InheritableSon is = new InheritableSon();
    }
}

运行结果:

InheritableFather:true
InheritableSon:true

现在,我们对 InheritableSon.java 进行修改:注释掉 “Inheritable 的 @Inherited 注解”。

import java.lang.annotation.Target;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Inherited;

/**
 * 自定义的Annotation。
 */
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
//@Inherited
@interface Inheritable
{
}

@Inheritable
class InheritableFather
{
    public InheritableFather() {
        // InheritableBase是否具有 Inheritable Annotation
        System.out.println("InheritableFather:"+InheritableFather.class.isAnnotationPresent(Inheritable.class));
    }
}

/**
 * InheritableSon 类只是继承于 InheritableFather,
 */
public class InheritableSon extends InheritableFather
{
    public InheritableSon() {
        super();    // 调用父类的构造函数
        // InheritableSon类是否具有 Inheritable Annotation
        System.out.println("InheritableSon:"+InheritableSon.class.isAnnotationPresent(Inheritable.class));
    }
   
    public static void main(String[] args)
    {
        InheritableSon is = new InheritableSon();
    }
}

运行结果:

InheritableFather:true
InheritableSon:false

从 Java 7 开始,额外添加了 3 个注解:
@SafeVarargs - Java 7 开始支持,忽略任何使用参数为泛型变量的方法或构造函数调用产生的警告。
@FunctionalInterface - Java 8 开始支持,标识一个匿名函数或函数式接口。
@Repeatable - Java 8 开始支持,标识某注解可以在同一个声明上使用多次。

(3)注解的作用

1.可以用于生成文档

当我们在注释中使用诸如@author,@since等注解时,通过javadoc工具会生成相应的文档说明,我们平时阅读的jdk文档就是这样生成的;
在这里插入图片描述
在这里插入图片描述
可以发现我们在代码注释中加上的注解通过javadoc后在相应位置增添了许多额外信息,这就是注解的生成文档的作用!!

2.进行编译检查

若某个方法被 @Override 的标注,则意味着该方法会覆盖父类中的同名方法。如果有方法被 @Override 标示,但父类中却没有"被 @Override 标注"的同名方法,则编译器会报错;实例如下:
在这里插入图片描述
这里Override继承Object类,由于@override注解标注了getString方法,表名该方法是重写的父类的,但是Object中没有该方法,因此会报错

3.代码分析

通过代码里标识的注解对代码进行分析(利用反射)

再讲解例子之前,我们先来学习一下注解的解析
所谓注解解析:就是可以获取注解中给属性赋的值

这里我们定义了一个注解:

@Target(ElementType.TYPE)
@Rentetion(RentetionPolicy.RUNTIME)
public @interface Pro{
	String className();
	String method();
}

在这里插入图片描述在这里插入图片描述
我们在类上使用Pro注解,并且赋予初值,
为了获取该值,我们首先要

  • 获取注解定义的位置的对象(class对象,method对象…)–如果注解定义在方法上,我们就要先通过反射获取method对象
  • 之后通过class类对象获取该注解对象,之后通过注解对象去调用方法得到属性值
    注意:getAnnotation()获取注解对象的本质是在内存中生成了一个注解接口的实现对象,并重写了方法,方法的返回值就是属性值;
    在这里插入图片描述

以下面实际例子:
小明定义了一个Calculator类,其中有一些方法,现在小明需要让你写一个测试框架,来帮助小明测试方法,要求是能说明

  • 哪些方法产生了异常
  • 异常的名称
  • 异常出现了哪些次数

这里自定义了一个Check注解
在这里插入图片描述
这是小明需要被测试的代码
在这里插入图片描述
下面是写的测试框架的代码:

public class TestCheck{
  public static void main(String[] args) {
  	//1.创建对象
  	Calculator c = new Calculator();
  	//2.获取字节码对象
  	Class  cls = c.getClass();
  	//3.获取所有方法
  	Method[] methods = cls.getMethods();


  	int num = 0;//出现异常的次数
  	BufferedWriter bw = new BufferedWriter(new FileWriter("bug.txt"));
  	for(Method method:methods){
  		//4.判断方法上是否有Check注解
  		if(method.isAnnotationPresent(Check.class)){
  			try{
  				//5.有注解,则执行方法
  				method.invoke(c);
  			}catch(Exception e){
  				number++;
  				//6.捕获异常,把异常信息记录到文件中
  				bw.write(method.getName()+"方法出异常了");
  				bw.newLine();
  				bw.write("异常产生的原因:"+e.getCause().getMessage());
  				bw.newLine();
  			}
  		}
  	}
  	bw.write("一共产生了"+number+"次异常");
  	bw.flush();
  	bw.close();
  }
}

当我们执行main方法,该框架代码就会去测试Calculator类,并把异常信息打印到文件中;

这里的注解就起到了代码分析的作用,我们给所需要的代码加上自定义注解,然后通过反射获取class对象,进而判断方法或类上是否标注了注解,若有则进行相应的操作;

二、自定义注解

1.怎样自定义注解?

很简单:
格式:

 元注解
 public @interface 自定义注解名{
	属性
}

我们先看jdk预定义的注解认识一下格式(以@SuppressWarnings举例):
在这里插入图片描述
在解释之前,我们先来认识一下注解的属性应该怎样定义?
我们已经知道,注解的本质是接口,既然是接口,就可以在接口中定义方法;
因此注解的属性是用方法来表示的;

String[] value();

这里注解中规定了方法的返回值只能是以下5种类型:

  • 8种基本数据类型(byte,short,int,long,float,double,char,boolean)
  • string类型
  • 枚举类型(enum)
  • 注解
  • 以上类型的数组
    所谓返回类型,可以理解为规定了属性的类型;
    注意:
    (1)只要注解中含有属性,那么在使用注解时必须按照属性的返回值来赋值
    (2)如果注解中只有一个属性,并且这个属性名的value,那么在赋值时,value可以省略不写,如果属性是数组,但是只有一个值,则{}可以省略不写
//@SuppressWarnings(value={"deprecation"})
@SuppressWarnings("all")
public class SuppressWarningTest {
    public static void doSomething(){
        Date date = new Date(113, 8, 26);
        System.out.println(date);
    }

    public static void main(String[] args) {
        doSomething();
    }
}

//@SuppressWarnings(value={“deprecation”})
@SuppressWarnings(“all”)
下面的写法是上面的简化
我们一把用@SuppressWarnings时属性一般赋值all,表示抑制全部警告

总结

(1)在开发中我们一般都是使用注解,很少会自定义注解,因此我们了解注解的一些相关语法即可; (2)我们写的注解一是给编译器使用,编译器识别注解,进行编译,二是给解析程序用,比如我们写的测试框架
  • 1
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值