Logo

郎哥编程

Java注解

2021-07-20 257

学习目标:注解可以为代码提供元数据,利用这些元数据。在编译阶段,编译器可以利用注解在编译过程中对代码进行检查。在程序运行阶段,可以利用反射技术解析注解并提取元数据,进行相应的操作和处理。主流的第三方Java开发框架基本都是用注解与反射技术来实现的。

 认识注解

注解(Annotation)可以添加到类、方法、变量、参数的声明前面,类似于注释。不过注解和注释是截然不同的两个概念。

注释仅对代码起到解释作用,方便程序员阅读代码。

注解虽然也能对代码起到解释作用,但注解的主要目的不是对代码进行注释。注解可以为代码提供元数据,利用这些元数据,注解可以起到如下作用:

(1)为编译器提供额外的代码信息;

(2)编译器可以利用注解在编译过程中对代码进行检查;

(3)在程序运行过程中,可以利用反射技术解析注解并提取元数据,进行相应的操作和处理;

(4)javadoc可以利用注解提供的元数据生成与源代码配套的帮助文档。

元数据是描述数据的数据,在这里可以理解为描述代码的数据,为代码提供额外的数据。例如,在前面课程的案例代码中,当子类方法重写父类方法时,会要求子类在重写父类方法的声明前面添加“@Override”,“@Override”就是一个已定义并用于编译检查的注解,这是一个标记注解,注解的元数据就是自身。在编译过程中,当编译器检测到某个方法添加了“@Override”注解,编译器就会检查当前方法在父类是否存在,如果父类没有该方法,编译器就会给出警告信息。

Java主流开发框架Spring内部就定义了大量注解,程序员可以在编写的Java代码中使用这些注解。当Spring加载类时会扫描这些注解,并根据注解内容进行程序的装配。

注解主要分为四大类:一类是来自Java内部定义的注解,也称为内置的注解,如@Override、@Deprecated等注解;一类是第三方框架定义的注解,如Spring框架的@Autowired、@Service等注解;一类是程序内部定义的注解,编写程序时自己来定义注解;一类是元注解,元注解在Java中也被定义,元注解主要用于对自定义的注解进行描述,也可以理解为注解的注解。

如何在程序内部定义注解呢?

在Java语言中,注解也是一个类,注解和类的定义基本相同,注解也有属性,但没有方法。注解的定义语法如下:

public @interface 注解名称
{
  类型  属性名称() default 默认值;
  ……
}

@interface是定义注解的修饰符,注解名称是自定义注解的名字,命名方式和类的命名方式相同。注解的主体内容由一对“{}”括起来,主体内容主要存放注解的属性,注解可以有多个注解属性,也可以没有注解属性,没有注解属性时,“{}”括号内不需要写任何内容。

注解属性的定义类似类方法的定义,属性名称前面是属性的类型,类型可以是基本数据类型、String、Class、枚举,也可以是前面类型的数组。类型后面是属性名称,类型和属性名称之间需要用空格隔开,紧跟属性名称是一对“()”括号,括号内不需要写任何内容。如果需要对属性设置默认值,可以在属性名称后面使用default关键字来设置属性的默认值。

案例15:建立Rate注解,Rate注解为其它类提供银行定期存款利率元数据。

在PUnit13项目新建annotation包,在annotation包下新建Rate注解。创建Rate类时,选择注解类型。

01.png

Rate类代码如下:

package annotation;
public @interface Rate {
    // 利率默认值0.03
    double rate() default 0.03;
}

Rate是注解的名称,Rate注解内部定义了一个double类型的属性rate,该属性的默认值为0.03。Rate注解可以为其它类提供银行定期存款利率元数据。

既然Rate注解为其它类提供银行定期存款利率数据,那么注解是如何使用的呢?其它类是如何读取注解并获取注解属性呢?这就要用到Java的反射技术。

使用注解

注解定义完后后,就可以使用注解了。注解的使用非常简单,注解可以添加在类、成员方法、成员变量、参数声明前面,每个类、成员方法、成员变量前面可以添加多个注解。注解添加语法如下:

@注解名称(属性名称1=值1,属性名称2=值2,……)

当注解有多个属性需要赋值时,每个属性赋值语句之间用英文逗号分隔。

例如:

@Rate(rate=0.035)
double dRate;

注解Rate的属性rate被赋值为0.035,该注解添加在变量dRate的前面,因此该注解作用于dRate变量。

如果注解的属性类型是数组,赋值方式为:

@Demo(value={20,30},name=”lihua”)

public void  showDemo() {};

其中,Demo为注解名称,该注解有两个属性,分别是value属性和name属性,value属性是int类型的数组,name属性是String类型。

Demo注解添加在showDemo()方法的前面,因此该注解作用于showDemo()方法。

在程序运行过程中,如果要读取注解和注解内部定义的属性,只能使用反射技术,如果没有反射技术,注解的作用就大打折扣了。

与反射技术相关的Class类、Constructor类、Method类、Field类都定义了一些方法用于操作注解。

Class类定义的常用注解方法说明如下:

●   Annotation[]  getAnnotations()

该方法用于返回Annotation类型的数组,数组包含类内部的所有注解。

●   <A extends Annotation> A  getAnnotation(Class<A> annotationClass)

该方法用于返回类中指定类型的注解,类型由annotationClass指定,如果没有该类型的注解,返回空。

类Constructor、Field和Method均继承了AccessibleObject类,该类定义了用于注解操作的方法,该类常用的注解操作方法说明如下:

●   Annotation[]  getAnnotations()

该方法用于返回Annotation类型的数组,数组包含当前元素上的所有注解。

●   <T extends Annotation> T  getAnnotation(Class<T> annotationClass)

该方法用于返回当前元素上指定类型的注解,类型由annotationClass指定,如果没有该类型的注解,返回空。

●   <T extends Annotation> T[]  getAnnotationsByType(Class<T> annotationClass)

该方法用于返回扩展Annotation类型的数组,该数组包含当前元素上指定annotationClass类型的注解。如果当前元素上没有该类型的注解,返回长度为0的空数组。

●   Annotation[]   getDeclaredAnnotations()

该方法返回Annotation类型的数组,数组包含当前元素上直接出现的所有注解,此方法忽略继承的注解。如果当前元素上没有注解,则返回值为长度为0的数组。

●   <T extends Annotation> T[]  getDeclaredAnnotationsByType (Class<T> annotationClass)

该方法用于返回扩展Annotation类型的数组,该数组包含当前元素直接出现的类型为annotationClass的注解。如果当前元素上没有该类型的注解,返回长度为0的空数组。

●   <T extends Annotation> T  getDeclaredAnnotation (Class<T> annotationClass)

该方法用于返回当前元素上直接出现的指定类型的注解,类型由annotationClass指定,如果没有该类型的注解,返回空。

●   boolean  isAnnotationPresent(Class<? extends Annotation> annotationClass)

该方法用于判断当前元素上是否有指定annotationClass类型的注解,如果有返回true,否则返回false。

案例16:建立Calculation类,该类用于银行定期存款利息计算。定义一个成员变量rate,在rate变量前面添加注解Rate,rate的值来自于Rate注解提供的元数据rate。定义一个方法interest(),用于计算本金利息。建立CalculationTest测试类,验证Rate注解的读取。

在annotation包下新建Calculation类。代码如下:

package annotation;
 
public class Calculation {
    // 添加Rate注解
    @Rate(rate=0.035)
    double dRate;
 
    public Calculation()
    {
        dRate = 0.0;
    }
 
    public double interest(double capital,int year)
    {
        return capital*dRate*year;
    }
 
    public double getdRate() {
        return dRate;
    }
 
    public void setdRate(double dRate) {
        this.dRate = dRate;
    }
 
}

注解的使用和类的使用一样,如果注解类和使用注解的类不在同一个包内,需要使用import语句将注解类导入。Rate注解添加到Calculation类dRate成员变量上,也可以同时添加到其它变量上。

在annotation包下新建CalculationTest类。代码如下:

package annotation;
 
import java.lang.reflect.Field;
 
public class CalculationTest {
    public static void main(String[] args) {
        // 实例化Calculation类
        Calculation  calcula = new  Calculation();
        // 获取Calculation类的Class对象
        Class calculaClass = calcula.getClass();
        try {
            //通过class.getDeclaredField(name)获取指定名称的成员变量
            Field field = calculaClass.getDeclaredField("dRate");
            //获取该成员变量上的Rate注解
            Rate rate = field.getAnnotation(Rate.class);
            // 判断注解是否获取成功
            if( null != rate )
            {
                // 注解对象直接访问rate属性
                calcula.setdRate(rate.rate());
                System.out.printf("定期存款年利息为:%.2f/%%\n",rate.rate()*100);
                System.out.println("15000元存款2年定期存款利息为:" + calcula.interest(15000,2));
            }
            else
            {
                System.out.println("获取Rate注解失败");
            }
 
        } catch (NoSuchFieldException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } catch (SecurityException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
 
 
 
    }
}

CalculationTest程序首先通过Calculation实例获取Class对象,通过Class对象的getDeclaredField()方法获取Calculation类成员变量dRate的Field对象,在通过Field类的getAnnotation()方法获取Rate注解,返回的注解对象引用赋值给Rate类型的局部变量rate。

在使用rate对象前,需要判断rate对象是否为空。若不为空,进入利息计算语句块,注解访问属性和类访问属性有一定的差别,注解通过对象名.属性()来访问注解的属性,在属性名称的后面要紧跟一对“()”.

程序执行结果如下图所示:

02.png

从执行结果来看,Rate注解获取失败。这是什么原因呢?这是因为在定义Rate注解时,没有给Rate注解添加注解作用范围,因此在程序运行过程就取不到Rate注解了。给注解添加作用范围,就需要涉及到元注解的概念。

元注解

元注解在JDK中已被定义,元注解主要用于对自定义的注解进行描述,也可以理解为注解的注解。

JDK定义了四种元注解,用来对注解的位置、作用范围、文档抽取、继承进行描述。这四种元注解分别是:@Target、@Retention、@Documented、@Inherited,下面分别对这四种元注解进行说明。

@Target

用于描述注解出现的位置,注解可以用于包、类、接口、类成员方法、类成员变量、方法参数、局部变量、枚举类型,JDK定义了ElementType枚举类型用于描述注解可以出现的位置,ElementType有如下枚举值:

ElementType.FIELD:注解仅用于添加到成员变量

ElementType.METHOD:注解仅用于添加到成员方法

ElementType.PARAMETER:注解仅用于添加到方法参数

ElementType.CONSTRUCTOR:注解仅用于添加到构造方法

ElementType.LOCAL_VARIABLE:注解仅用于添加到局部变量

ElementType.ANNOTATION_TYPE:注解仅用于添加到注解(元注解)

ElementType.PACKAGE:注解仅用于添加到包

描述注解可以使用多个枚举值。例如:如果希望注解添加到成员方法和成员变量上,可以使用ElementType.FIELD和ElementType.METHOD枚举值来描述注解。

示例:

@Target(ElementType.METHOD, ElementType.FIELD )
public @interface Demo {
    ......
}

@Retention

用于描述注解的作用范围,即注解的有效范围。JDK定义了三种有效范围,分别是:注解作用于源代码(编译器可读取注解)、注解作用于类文件(在类文件中可读取注解)、注解作用于运行过程(用反射技术读取注解)。JDK定义了RetentionPolicy枚举类型用于描述注解作用范围,RetentionPolicy有如下枚举值:

RetentionPolicy.SOURCE:注解作用于源代码

RetentionPolicy.CLASS:注解作用于类文件

RetentionPolicy.RUNTIME:注解作用于运行时

示例:

@ Retention (RetentionPolicy.RUNTIME )
public @interface Demo {
    ......
}

当注解作用范围选择RetentionPolicy.RUNTIME时,作用范围自然就满足类文件和源代码。

@Documented

使用@Documented描述的注解可以被javadoc工具抽取作为程序文档。它是一个标记注解,没有属性。

示例:

@ Documented
public @interface Demo {
    ......
}

@Inherited

使用@Inherited描述的注解具有继承性,使用@Inherited注解的类,其子类将自动拥有该注解。它是一个标记注解,没有属性。

学习了元注解后,就可以解决前面一课案例读取Rate注解失败的问题,需要在Rate注解前面添加@ Retention (RetentionPolicy.RUNTIME )元注解。

为了不影响前面一课案例的源代码,本课重新编写案例代码。

案例17:建立NewRate注解,NewRate注解为其它类提供银行定期存款利率元数据。建立NewCalculation类,该类用于银行定期存款利息计算。定义一个成员变量rate,在rate变量前面添加注解NewRate,rate的值来自于NewRate注解提供的元数据rate。定义一个方法interest(),用于计算本金利息。建立NewCalculationTest测试类,验证NewRate注解的读取。

在annotation包下新建NewRate注解类。代码如下:

package annotation; 
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
 
//注解存在于运行时
@Retention(RetentionPolicy.RUNTIME)
public @interface NewRate {
    // 利率默认值0.03
    double rate() default 0.03;
 
}

在NewRate注解前面添加元注解@Retention,元注解的值选择RetentionPolicy.RUNTIME,即注解作用范围为运行时。@Retention元注解和RetentionPolicy枚举类型都在java.lang.annotation包内,需要使用import语句导入。

在annotation包下新建NewCalculation类。代码如下:

package annotation; 
public class NewCalculation {
    // 添加Rate注解
    @NewRate(rate = 0.035)
    double dRate;
 
    public NewCalculation()
    {
        dRate = 0.0;
    }
  
    public double interest(double capital, int year) {
        return capital * dRate * year;
    }
  
    public double getdRate() {
        return dRate;
    }
 
  
    public void setdRate(double dRate) {
        this.dRate = dRate;
    }
 
 
}

如果注解类和使用注解的类不在同一个包内,需要使用import语句将注解类导入。NewRate注解添加到NewCalculation类dRate成员变量上,也可以同时添加到其它变量上。

在annotation包下新建NewCalculationTest测试类。代码如下:

package annotation;
 
import java.lang.reflect.Field;
 
public class NewCalculationTest {
    public static void main(String[] args) {
        // 实例化Calculation类
        NewCalculation calcula = new NewCalculation();
        // 获取Calculation类的Class对象
        Class calculaClass = calcula.getClass();
        try {
            // 通过class.getDeclaredField(name)获取指定名称的成员变量
            Field field = calculaClass.getDeclaredField("dRate");
            // 获取该成员变量上的Rate注解
            NewRate rate = field.getAnnotation(NewRate.class);
            // 判断注解是否获取成功
            if (null != rate) {
                // 注解对象直接访问rate属性
                calcula.setdRate(rate.rate());
                System.out.printf("定期存款年利息为:%.2f/%%\n", rate.rate() * 100);
                System.out.println("15000元存款2年定期存款利息为:" + calcula.interest(15000, 2));
            } else {
                System.out.println("获取Rate注解失败");
            }
 
        } catch (NoSuchFieldException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } catch (SecurityException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
 
    }
}

NewCalculationTest程序首先通过NewCalculation实例获取Class对象,通过Class对象的getDeclaredField()方法获取NewCalculation类成员变量dRate的Field对象,在通过Field类的getAnnotation()方法获取NewRate注解,返回的注解对象引用赋值给NewRate类型的局部变量rate。

程序执行结果如下图所示:

03.png

从程序执行结果可以看出,NewCalculation类的NewRate注解的属性被成功读取,并设置到calcula对象的dRate成员变量。

内置的注解

Java内置的注解主要给编译器提供类的编译信息,主要有@Override、@Deprecated、@SuppressWarnings注解,这三个注解java程序员应该经常见到,下面对这三个注解分别进行说明。

@Override

这是一个标记注解,注解的元数据就是自身。该注解的出现位置是在类成员方法前面,通知编译器使用该注解的方法重写了父类的方法。在编译过程中,当编译器检测到某个方法添加了“@Override”注解时,编译器就会检查当前方法在父类是否存在,如果父类没有该方法,编译器就会给出警告信息。

@Deprecated

这也是一个标记注解,用来通知编译器使用该注解的类、方法、属性已经过时(废弃不用),编译器会显示警告信息,提醒开发者正在使用一个过时的类、方法或属性。

@SuppressWarnings

该注解用来阻止编译器发出警告信息。编译器在编译代码时,会输出一些安全警告信息,但会忽略@SuppressWarnings注解标记的元素产生的警告信息。

该注解可用于标记类、成员变量、方法、参数和局部变量,注解的作用范围为源代码级,注解信息不会保留在class文件中,注解的属性为value,是String类型的数组,可以忽略多种类型的警告信息。

代码在线纠错(通义千问 qwen-max)

支持粘贴多个代码文件,提交后由阿里云通义千问自动分析代码漏洞、语法错误、逻辑问题并给出修改建议。
您已解锁 AI 代码纠错功能,可正常使用!

评论区

登录 后发表评论
暂无评论