Java 注解彻底解析

关于注解首先引入官方文档的一句话:Java 注解用于为 Java 代码提供元数据。做为元数据,注解不直接影响你的代码执行,但也有一些类型的注解实际上能够用于这一目的。Java 注解是从 Java5 开始添加到 Java 的。看完这句话也许你仍是一脸懵逼,接下我将从注解的定义、元注解、注解属性、自定义注解、注解解析JDK 提供的注解这几个方面再次了解注解(Annotation)git

注解的定义

  • 平常开发中新建Java类,咱们使用class、interface比较多,而注解和它们同样,也是一种类的类型,他是用的修饰符为 @interface

Java中新建类

注解类的写法

  • 咱们新建一个注解MyTestAnnotation
public @interface MyTestAnnotation {

}
复制代码
  • 接着咱们就能够在类或者方法上做用咱们刚刚新建的注解
@MyTestAnnotation
public class test {
   @MyTestAnnotation
   public static void main(String[] args){
   }
}
复制代码
  • 以上咱们只是了解了注解的写法,可是咱们定义的注解中还没写任何代码,如今这个注解毫无心义,要如何使注解工做呢?接下来咱们接着了解元注解。

元注解

  • 元注解顾名思义咱们能够理解为注解的注解,它是做用在注解中,方便咱们使用注解实现想要的功能。元注解分别有@Retention、 @Target、 @Document、 @Inherited和@Repeatable(JDK1.8加入)五种。

@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 MyTestAnnotation {

}
复制代码

@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 MyTestAnnotation {

}
复制代码

@Documented

  • Document的英文意思是文档。它的做用是可以将注解中的元素包含到 Javadoc 中去。

@Inherited

  • Inherited的英文意思是继承,可是这个继承和咱们平时理解的继承大同小异,一个被@Inherited注解了的注解修饰了一个父类,若是他的子类没有被其余注解修饰,则它的子类也继承了父类的注解。
  • 下面咱们来看个@Inherited注解例子
/**自定义注解*/
@Documented
@Inherited
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface MyTestAnnotation {
}
/**父类标注自定义注解*/
@MyTestAnnotation
public class Father {
}
/**子类*/
public class Son extends Father {
}
/**测试子类获取父类自定义注解*/
public class test {
   public static void main(String[] args){

      //获取Son的class对象
       Class<Son> sonClass = Son.class;
      // 获取Son类上的注解MyTestAnnotation能够执行成功
      MyTestAnnotation annotation = sonClass.getAnnotation(MyTestAnnotation.class);
   }
}
复制代码

@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 {
}
复制代码
  • 经过上面的例子,你可能会有一个疑问,游戏注解中括号的变量是啥,其实这和游戏注解中定义的属性对应。接下来咱们继续学习注解的属性。

注解的属性

  • 经过上一小节@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();
}
复制代码

注解的本质

  • 注解的本质就是一个Annotation接口
/**Annotation接口源码*/
public interface Annotation {
   
    boolean equals(Object obj);

    int hashCode();
    
    Class<? extends Annotation> annotationType();
}
复制代码
  • 经过以上源码,咱们知道注解自己就是Annotation接口的子接口,也就是说注解中实际上是能够有属性和方法,可是接口中的属性都是static final的,对于注解来讲没什么意义,而咱们定义接口的方法就至关于注解的属性,也就对应了前面说的为何注解只有属性成员变量,其实他就是接口的方法,这就是为何成员变量会有括号,不一样于接口咱们能够在注解的括号中给成员变量赋值。

注解属性类型

  • 注解属性类型能够有如下列出的类型
  • 1.基本数据类型
  • 2.String
  • 3.枚举类型
  • 4.注解类型
  • 5.Class类型
  • 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 {
}
复制代码

获取注解属性

  • 前面咱们说了不少注解如何定义,放在哪,如今咱们能够开始学习注解属性的提取了,这才是使用注解的关键,获取属性的值才是使用注解的目的。
  • 若是获取注解属性,固然是反射啦,主要有三个基本的方法
/**是否存在对应 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)
public class test {
   public static void main(String[] args) throws NoSuchMethodException {

        /**
         * 获取类注解属性
         */
        Class<Father> fatherClass = Father.class;
        boolean annotationPresent = fatherClass.isAnnotationPresent(MyTestAnnotation.class);
        if(annotationPresent){
            MyTestAnnotation annotation = fatherClass.getAnnotation(MyTestAnnotation.class);
            System.out.println(annotation.name());
            System.out.println(annotation.age());
        }

        /**
         * 获取方法注解属性
         */
        try {
            Field age = fatherClass.getDeclaredField("age");
            boolean annotationPresent1 = age.isAnnotationPresent(Age.class);
            if(annotationPresent1){
                Age annotation = age.getAnnotation(Age.class);
                System.out.println(annotation.value());
            }

            Method 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 (NoSuchFieldException e) {
            e.printStackTrace();
        }
    }
}
复制代码

运行结果:github

获取注解属性运行结果

JDK 提供的注解

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

注解做用与应用

  • 如今咱们再次回头看看开头官方文档的那句描述

Java 注解用于为 Java 代码提供元数据。做为元数据,注解不直接影响你的代码执行,但也有一些类型的注解实际上能够用于这一目的。数组

  • 通过咱们前面的了解,注解实际上是个很方便的东西,它存活的时间,做用的区域均可以由你方便设置,只是你用注解来干吗的问题

使用注解进行参数配置

  • 下面咱们看一个银行转帐的例子,假设银行有个转帐业务,转帐的限额可能会根据汇率的变化而变化,咱们能够利用注解灵活配置转帐的限额,而不用每次都去修改咱们的业务代码。
/**定义限额注解*/
@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);
    }
}
复制代码

运行结果:bash

转帐处理运行结果

  • 经过上面的例子,只要汇率变化,咱们就改变注解的配置值就能够直接改变当前最大限额。

第三方框架的应用

  • 做为一个Android 开发者,日常咱们所使用的第三方框架ButterKnife,Retrofit2,Dagger2等都有注解的应用,若是咱们要了解这些框架的原理,则注解的基础知识则是必不可少的。

注解的做用

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

最后说点

到此,对于Java中注解的解析就结束了。最后,也很是感谢您阅读个人文章,文章中若是有错误,请你们给我提出来,你们一块儿学习进步,若是以为个人文章给予你帮助,也请给我一个喜欢和关注,同时也欢迎访问个人我的博客框架

相关文章
相关标签/搜索