首页 专题 文章 代码 归档
Java 注解基础
2020.03.09 10:03 2020.03.09 10:03

1. 注解

什么是注解(Annotation)?注解是放在Java源码的类、方法、字段、参数前的一种特殊“注释”:

1.1. 概念和分类

概念:说明程序的。给计算机看的

  • 注释:用文字描述程序的。给程序员看的

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

  • 概念描述:

    • JDK1.5之后的新特性
    • 说明程序的
    • 使用注解:@注解名称
    • 作用分类: ①编写文档:通过代码里标识的注解生成文档【生成文档doc文档】我们自己不能改的,比如@since ②代码分析:通过代码里标识的注解对代码进行分析【使用反射】 ③编译检查:通过代码里标识的注解让编译器能够实现基本的编译检查【Override】我们自己不能改的,比如@override

1.2. 注解写法

注解写法很简单,使用@interface修饰即可;

在IDEA中,您可直接选择新建注解,如下:

截图-1583716848

当然,你可直接如下写法也可:

public @interface MyTest {
}

接着我们新建一个Main方法,在其上使用我们自己的新建的注解:

public class Main {
    @MyTest
    public static void main(String[] args) {

    }
}

以上我们只是了解了注解的写法,但是我们定义的注解中还没写任何代码,现在这个注解毫无意义,要如何使注解工作呢?接下来我们接着了解元注解。

2. 元注解

元注解顾名思义我们可以理解为注解的注解,它是作用在注解中,方便我们使用注解实现想要的功能。

元注解分别有@Retention@Target@Document@Inherited@Repeatable(JDK1.8加入)五种。

2.1. @Retention

  • Retention英文意思有保留、保持的意思,它表示注解存在阶段是保留在源码(编译期),字节码(类加载)或者运行期(JVM中运行)。在@Retention注解中使用枚举RetentionPolicy来表示注解保留时期
  • @Retention(RetentionPolicy.SOURCE),注解仅存在于源码中,在class字节码文件中不包含
  • @Retention(RetentionPolicy.CLASS), 默认的保留策略,注解会在class字节码文件中存在,但运行时无法获得
  • @Retention(RetentionPolicy.RUNTIME), 注解会在class字节码文件中存在,在运行时可以通过反射获取到 如果我们是自定义注解,则通过前面分析,我们自定义注解如果只存着源码中或者字节码文件中就无法发挥作用,而在运行期间能获取到注解才能实现我们目的,所以自定义注解中肯定是使用@Retention(RetentionPolicy.RUNTIME)
@Retention(RetentionPolicy.RUNTIME)
public @interface MyTest {
}

2.2. @Target

Target的英文意思是目标,这也很容易理解,使用@Target元注解表示我们的注解作用的范围就比较具体了,可以是类,方法,方法参数变量等,同样也是通过枚举类ElementType表达作用类型

  • @Target(ElementType.TYPE) 作用接口、类、枚举、注解
  • @Target(ElementType.FIELD) 作用属性字段、枚举的常量
  • @Target(ElementType.METHOD) 作用方法
  • @Target(ElementType.PARAMETER) 作用方法参数
  • @Target(ElementType.CONSTRUCTOR) 作用构造函数
  • @Target(ElementType.LOCAL_VARIABLE)作用局部变量
  • @Target(ElementType.ANNOTATION_TYPE)作用于注解(@Retention注解中就使用该属性)
  • @Target(ElementType.PACKAGE) 作用于包
  • @Target(ElementType.TYPE_PARAMETER) 作用于类型泛型,即泛型方法、泛型类、泛型接口 (jdk1.8加入)
  • @Target(ElementType.TYPE_USE) 类型使用.可以用于标注任意类型除了 class (jdk1.8加入)

一般比较常用的是ElementType.TYPE类型

所以,此时,我们自定义的注解就成了:

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface MyTest {
}

2.3. @Documented

Document的英文意思是文档。它的作用是能够将注解中的元素包含到 Javadoc 中去。

2.4. @Inherited

Inherited的英文意思是继承,但是这个继承和我们平时理解的继承大同小异,一个被@Inherited注解了的注解修饰了一个父类,如果他的子类没有被其他注解修饰,则它的子类也继承了父类的注解。

实例:

此时我们的注解成了:

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

测试:

@MyTest
class father {

}

class son extends father {

}


public class Main {
    public static void main(String[] args) {
        Class<son> sonClass = son.class;
        MyTest annotation = sonClass.getAnnotation(MyTest.class);
        System.out.println("annotation = " + annotation);
    }
}

解释:

父类father上使用了注解@MyTest,子类没有使用,但是由于父类的注解的元注解有@Inherited,所以子类son也继承了父类的所有注解;

2.5. @Repeatable

Repeatable的英文意思是可重复的。顾名思义说明被这个元注解修饰的注解可以同时作用一个对象多次,但是每次作用注解又可以代表不同的含义。

/**一个人喜欢玩游戏,他喜欢玩英雄联盟,绝地求生,极品飞车,尘埃4等,则我们需要定义一个人的注解,他属性代表喜欢玩游戏集合,一个游戏注解,游戏属性代表游戏名称*/
/**玩家注解*/
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface People {
    Game[] value() ;
}
/**游戏注解*/
@Repeatable(People.class)
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface Game {
    String value() default "";
}
/**玩游戏类*/
@Game(value = "LOL")
@Game(value = "PUBG")
@Game(value = "NFS")
@Game(value = "Dirt4")
public class PlayGame {
}

游戏注解中括号的变量是啥,其实这和游戏注解中定义的属性对应。接下来我们继续学习注解的属性。

3. 注解的属性

节@Repeatable注解的例子,我们说到注解的属性。

注解的属性其实和类中定义的变量有异曲同工之处,只是注解中的变量都是成员变量(属性),并且注解中是没有方法的,只有成员变量,变量名就是使用注解括号中对应的参数名,变量返回值注解括号中对应参数类型。

相信这会你应该会对上面的例子有一个更深的认识。而@Repeatable注解中的变量则类型则是对应Annotation(接口)的泛型Class。

/**注解Repeatable源码*/
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Repeatable {
    /**
     * Indicates the <em>containing annotation type</em> for the
     * repeatable annotation type.
     * @return the containing annotation type
     */
    Class<? extends Annotation> value();
}

4. 注解的本质

注解的本质就是一个Annotation接口

/**Annotation鎺ュ彛婧愮爜*/
public interface Annotation {

    boolean equals(Object obj);

    int hashCode();

    Class<? extends Annotation> annotationType();
}

通过以上源码,我们知道注解本身就是Annotation接口的子接口,也就是说注解中其实是可以有属性和方法,但是接口中的属性都是static final的,对于注解来说没什么意义,而我们定义接口的方法就相当于注解的属性,也就对应了前面说的为什么注解只有属性成员变量,其实他就是接口的方法,这就是为什么成员变量会有括号,不同于接口我们可以在注解的括号中给成员变量赋值。

5. 注解属性的类型

注解属性类型可以有以下列出的类型

  1. 基本数据类型
  2. String
  3. 枚举类型
  4. 注解类型
  5. Class类型
  6. 以上类型的一维数组类型

6. 注解成员变量赋值

如果注解又多个属性,则可以在注解括号中用“,”号隔开分别给对应的属性赋值,如下例子,注解在父类中赋值属性

@Documented
@Inherited
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface MyTestAnnotation {
    String name() default "mao";
    int age() default 18;
}

@MyTestAnnotation(name = "father",age = 50)
public class Father {
}

7. 获取注解属性

前面我们讲了很多注解如何定义,放在哪,现在我们可以开始学习注解属性的提取了,这才是使用注解的关键,获取属性的值才是使用注解的目的。

如果获取注解属性,当然是反射啦,主要有三个基本的方法:

Tips:注解+反射才是最佳配合,这是很多Java框架的基础;

/**是否存在对应 Annotation 对象*/
 public boolean isAnnotationPresent(Class<? extends Annotation> annotationClass) {
       return GenericDeclaration.super.isAnnotationPresent(annotationClass);
   }

/**获取 Annotation 对象*/
   public <A extends Annotation> A getAnnotation(Class<A> annotationClass) {
       Objects.requireNonNull(annotationClass);

       return (A) annotationData().annotations.get(annotationClass);
   }
/**获取所有 Annotation 对象数组*/   
public Annotation[] getAnnotations() {
       return AnnotationParser.toArray(annotationData().annotations);
   }    

下面的例子是获取一下注解属性,在获取之前,我们自定义的注解必须使用元注解@Retention(RetentionPolicy.RUNTIME)修饰,这样在运行时才能获取到!

@MyTest
public class Main {
    public static void main(String[] args) {
        /*获取类注解属性*/

        Class<Father> fatherClass = Father.class;
        boolean annotationPresent = fatherClass.isAnnotationPresent(MyTest.class);
        if (annotationPresent) {
            MyTest annotation = fatherClass.getAnnotation(MyTest.class);
            int age = annotation.age();
            String name = annotation.name();
            System.out.println("age = " + age);
            System.out.println("name = " + name);
            /*age = 50 =========== name = Misiai*/
        }

        /*获取方法注解属性*/
        Method play = null;
        try {
            play = PlayGame.class.getDeclaredMethod("play");
            if (play != null) {
                People annotation2 = play.getAnnotation(People.class);
                Game[] value = annotation2.value();
                for (Game game : value) {
                    System.out.println(game.value());
                }
            }
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        }

    }
}

8. Java提供的注解

注解 作用 注意事项
@Override 它是用来描述当前方法是一个重写的方法,在编译阶段对方法进行检查 jdk1.5中它只能描述继承中的重写,jdk1.6中它可以描述接口实现的重写,也能描述类的继承的重写
@Deprecated 它是用于描述当前方法是一个过时的方法
@SuppressWarnings 对程序中的警告去除。

9. 注解作用与应用

官方文档说:Java 注解用于为 Java 代码提供元数据。作为元数据,注解不直接影响你的代码执行,但也有一些类型的注解实际上可以用于这一目的。

经过前面的了解,注解其实是个很方便的东西,它存活的时间,作用的区域都可以由你方便设置,现在的问题只是你用注解来干嘛的问题

9.1. 进行参数配置

下面是一个银行转账的例子,假设银行有个转账业务,转账的限额可能会根据汇率的变化而变化,我们可以利用注解灵活配置转账的限额,而不用每次都去修改我们的业务代码。

/**定义限额注解*/
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface BankTransferMoney {
    double maxMoney() default 10000;
}
/**转账处理业务类*/
public class BankService {
    /**
     * @param money 转账金额
     */
    @BankTransferMoney(maxMoney = 15000)
    public static void TransferMoney(double money){
        System.out.println(processAnnotationMoney(money));

    }
    private static String processAnnotationMoney(double money) {
        try {
            Method transferMoney = BankService.class.getDeclaredMethod("TransferMoney",double.class);
            boolean annotationPresent = transferMoney.isAnnotationPresent(BankTransferMoney.class);
            if(annotationPresent){
                BankTransferMoney annotation = transferMoney.getAnnotation(BankTransferMoney.class);
                double l = annotation.maxMoney();
                if(money>l){
                   return "转账金额大于限额,转账失败";
                }else {
                    return"转账金额为:"+money+",转账成功";
                }
            }
        } catch ( NoSuchMethodException e) {
            e.printStackTrace();
        }
        return "转账处理失败";
    }
    public static void main(String[] args){
        TransferMoney(10000);
    }
}

9.2. 第三方框架的应用

现在不管是java的web框架,还是Android那一堆框架,基本都要使用注解进行开发,所以知道注解的原理、使用,我们才能更好的理解这些框架;

9.3. 注解的作用

  • 提供信息给编译器: 编译器可以利用注解来检测出错误或者警告信息,打印出日志。
  • 编译阶段时的处理: 软件工具可以用来利用注解信息来自动生成代码、文档或者做其它相应的自动处理。
  • 运行时处理: 某些注解可以在程序运行的时候接受代码的提取,自动做相应的操作。
  • 正如官方文档的那句话所说,注解能够提供元数据,转账例子中处理获取注解值的过程是我们开发者直接写的注解提取逻辑,处理提取和处理 Annotation 的代码统称为 APT(Annotation Processing Tool)。上面转账例子中的processAnnotationMoney方法就可以理解为APT工具类。

本文参考自:https://www.jianshu.com/p/9471d6bcf4cf

本节阅读完毕! (分享
二维码图片 扫描关注我们哟