• Java 元注解 使用示例


    Java元注解

    注解的注解,称为元注解。

    @Target

    作用:用于描述注解的使用范围(即:被描述的注解可以用在什么地方)。

    所修饰的对象范围:

    ElementType:

    • TYPE:类、接口(包括注解类型)和枚举的声明
    • FIELD:字段声明(包括枚举常量)
    • METHOD:方法声明
    • PARAMETER:参数声明
    • CONSTRUCTOR:构造函数声明
    • LOCAL_VARIABLE:本地变量声明
    • ANNOTATION_TYPE:注解类型声明
    • PACKAGE:包声明
    • TYPE_PARAMETER:类型参数声明,JavaSE 8引进,可以应用于类的泛型声明之处
    • TYPE_USE:能标注任何类型名称 JavaSE 8引进,此类型包括类型声明和类型参数声明,是为了方便设计者进行类型检查

    Annotation类型的声明中使用了target可更加明晰其修饰的目标。

    注意:如果一个注解没有指定@Target注解,则此注解可以用于除了TYPE_PARAMETERTYPE_USE以外的任何地方。

    /**
     * <p> 描述 : 使用TYPE_PARAMETER类型,该注解只能在类型参数声明时使用
     * @author : blackcat
     * @date  : 2020/8/6 15:04
    */
    @Retention(RetentionPolicy.RUNTIME)
    @Target(ElementType.TYPE_PARAMETER)
    public @interface TypeParammeterAnnotation {
    }
    
    /**
     * <p> 描述 : 使用TYPE_USE类型,该注解能标注任何类型名称
     * @author : blackcat
     * @date  : 2020/8/6 15:38
    */
    @Target(ElementType.TYPE_USE)
    @Retention(RetentionPolicy.RUNTIME)
    public @interface TypeUseAnnotation {
    
    }
    
    /**
     * <p> 描述 : 泛型类型声明时,使用使用TYPE_PARAMETER类型,编译通过
     * @author : blackcat
     * @date  : 2020/8/6 15:05
    */
    public class TestTypeParameter<@TypeParammeterAnnotation T> {
        /** 使用TYPE_PARAMETER类型,会编译不通过 */
        /*public @TypeParammeterAnnotation T test(@TypeParammeterAnnotation T a){
            new ArrayList<@TypeParammeterAnnotation String>();
                return a;
        }*/
    }
    
    /**
     * <p> 描述 : ElementType.TYPE_USE使用示例
     *         使用TYPE_USE类型,该注解能标注任何类型名称
     * @author : blackcat
     * @date  : 2020/8/6 15:33
    */
    public class TestTypeUse {
    
        /** 泛型类型声明时,使用TYPE_USE类型,编译通过 */
        public static @TypeUseAnnotation class TypeUseClass<@TypeUseAnnotation T> extends @TypeUseAnnotation Object {
            /** 使用TYPE_USE类型,编译通过 */
            public void foo(@TypeUseAnnotation T t) throws @TypeUseAnnotation Exception {
    
            }
        }
    
        // 如下注解的使用都是合法的
        @SuppressWarnings({ "rawtypes", "unused", "resource" })
        public static void main(String[] args) throws Exception {
            TypeUseClass<@TypeUseAnnotation String> typeUseClass = new @TypeUseAnnotation TypeUseClass<>();
            typeUseClass.foo("");
            List<@TypeUseAnnotation Comparable> list1 = new ArrayList<>();
            List<? extends Comparable> list2 = new ArrayList<>();
            @TypeUseAnnotation String text = (@TypeUseAnnotation String)new Object();
            java.util. @TypeUseAnnotation Scanner console = new java.util.@TypeUseAnnotation Scanner(System.in);
        }
    }
    
    /**
     * <p> 描述 : ElementType.METHOD只能修饰方法。
     * @author : blackcat
     * @date  : 2020/8/6 16:37
    */
    @Target(ElementType.METHOD)
    public @interface MethodAnnotation {
    }
    
    /**
     * <p> 描述 : 测试
     * @author : blackcat
     * @date  : 2020/8/6 16:38
    */
    public class TestMethodAnnotation {
    
        /** 编译不通过ElementType.METHOD 不能修饰成员变量,只能修饰方法。 */
        /*@MethodAnnotation
        String name;*/
    
        @MethodAnnotation
        public void test(){}
    }
    

    对于其他的ElementType注解元素,看ElementType 类的注释即可知道如何使用。

    @Retention

    这种类型的注解会被保留到那个阶段. 有三个值:

    • RetentionPolicy.SOURCE —— 注解只保留在源文件,当Java文件编译成class文件的时候,注解被遗弃;
    • RetentionPolicy.CLASS —— 注解被保留到class文件,但Jvm加载class文件时候被遗弃,这是默认的生命周期;
    • RetentionPolicy.RUNTIME —— 注解不仅被保存到class文件中,Jvm加载class文件之后,仍然存在;

    这3个生命周期分别对应于:Java源文件(.java文件) ---> .class文件 ---> 内存中的字节码。

    那怎么来选择合适的注解生命周期呢?
    首先要明确生命周期长度 SOURCE < CLASS < RUNTIME ,所以前者能作用的地方后者一定也能作用。

    • 一般如果需要在运行时去动态获取注解信息,那只能用 RUNTIME 注解,比如@Deprecated使用RUNTIME注解;
    • 如果要在编译时进行一些预处理操作,比如生成一些辅助代码(如 ButterKnife),就用 CLASS注解;
    • 如果只是做一些检查性的操作,比如 @Override@SuppressWarnings,使用SOURCE 注解。

    注解@Override用在方法上,当我们想重写一个方法时,在方法上加@Override,当我们方法的名字出错时,编译器就会报错。
    注解@Deprecated,用来表示某个类或属性或方法已经过时,不想别人再用时,在属性和方法上用@Deprecated修饰。
    注解@SuppressWarnings用来压制程序中出来的警告,比如在没有用泛型或是方法已经过时的时候。

    /**
     * <p> 描述 : 示不仅会在编译后的class文件中存在,而且在运行时保留。
     *         因此它们主要用于反射场景,可以通过getAnnotation方法获取。
     * @author : blackcat
     * @date  : 2020/8/6 16:08
    */
    @Target(ElementType.TYPE)
    @Retention(RetentionPolicy.RUNTIME)
    public @interface Country {
        /** 国家名称 */
        String name();
        /** 国家语言 */
        String[] languages();
    }
    
    /**
     * <p> 描述 : 默认策略,表示注解会在编译后的class文件中存在,但是在运行时,不会被VM保留。
     * @author : blackcat
     * @date  : 2020/8/6 16:09
    */
    @Target(ElementType.TYPE)
    @Retention(RetentionPolicy.CLASS)
    public @interface Region {
        /** 地区名称 */
        String name();
        /** 所属国家 */
        String country();
    }
    
    /**
     * <p> 描述 : 表示注解会在编译时被丢弃
     * @author : blackcat
     * @date  : 2020/8/6 16:10
    */
    @Target(ElementType.TYPE)
    @Retention(RetentionPolicy.SOURCE)
    public @interface Home {
        /** 成员 */
        String[] members();
        /** 地址 */
        String address();
    }
    
    /**
    * <p> 描述 : 测试
    * @author : blackcat
    * @date  : 2020/8/6 16:14
    */
    @Country(
        name = "China",
        languages = {"Chinese"}
    )
    @Region(
        name = "GuangDong",
        country = "China"
    )
    @Home(
        members = {"Wolffy","Wolnie","Wilie"},
        address = "Qingqing grasslands"
    )
    public class TestAnnotation {
        public static void main(String[] args) {
            Annotation[] annotations = TestAnnotation.class.getAnnotations();
            System.out.println("获取能保留到运行时的注解:");
            for (Annotation annotation :annotations){
           		System.out.println(annotation.toString());
            }
            /* 
            print:
            获取能保留到运行时的注解:
            @com.blackcat.metaannotation.retention.Country(name=China, languages=[Chinese])
            */
        } 
    }
    

    @Documented

    是一个标记注解,没有成员变量。用 @Documented 注解修饰的注解类会被 JavaDoc 工具提取成文档。

    默认情况下,JavaDoc 是不包括注解的,但如果声明注解时指定了 @Documented,就会被 JavaDoc 之类的工具处理,所以注解类型信息就会被包括在生成的帮助文档中。

    /**
     * <p> 描述 : 用 @Documented 注解修饰的注解类会被 JavaDoc 工具提取成文档
     * @author : blackcat
     * @date  : 2020/8/6 16:47
    */
    @Documented
    @Target({ ElementType.TYPE, ElementType.METHOD })
    public @interface MyDocumented {
        String value() default "这是@Documented注解";
    }
    
    /**
     * <p> 描述 :测试document
     * @author : blackcat
     * @date  : 2020/8/6 16:48
    */
    @MyDocumented
    public class TestDocumented {
        /**
         * 测试document
         */
        @MyDocumented
        public String Test() {
            return "测试document";
        }
    }
    

    打开 Java 文件所在的目录,分别输入如下两条命令行:

    javac -encoding utf-8 MyDocumented.java TestDocumented.java
    javadoc -encoding utf-8 -d doc MyDocumented.java TestDocumented.java
    

    在当前目录下生成的doc文件夹里的index.html可以看到以下内容。

    img

    @Inherited

    是一个标记注解,用来指定该注解可以被继承。

    使用 @Inherited 注解的 Class 类,表示这个注解可以被用于该 Class 类的子类。就是说如果某个类使用了被 @Inherited 修饰的注解,则其子类将自动具有该注解。

    注意:此注解只对注解标记的超类有效,对接口是无效的。

    /**
     * <p> 描述 : 简称 用@Inherited标记的注解
     *          注意:此注解只对注解标记的超类(被继承的类-父类)有效,对接口是无效的。
     * @author : blackcat
     * @date  : 2020/8/7 9:10
     */
    @Inherited
    @Retention(RetentionPolicy.RUNTIME)
    @Target(ElementType.TYPE)
    public @interface Abbreviation {
        /** 简称 */
        String value();
    }
    
    /**
     * <p> 描述 : 名称  用@Inherited标记的注解
     *          注意:此注解只对注解标记的超类(被继承的类-父类)有效,对接口是无效的。
     * @author : blackcat
     * @date  : 2020/8/7 9:10
    */
    @Inherited
    @Retention(RetentionPolicy.RUNTIME)
    @Target(ElementType.TYPE)
    public @interface Name {
        /** 名称 */
        String value();
    }
    
    /**
     * <p> 描述 : 大写 没有用@Inherited标记的注解
     * @author : blackcat
     * @date  : 2020/8/7 9:11
    */
    @Retention(RetentionPolicy.RUNTIME)
    @Target(ElementType.TYPE)
    public @interface UpperCaseName {
        /** 大写英文名称 */
        String value();
    }
    

    声明一些接口和类用于举例:

    /**
     * <p> 描述 : 有机体
     * @author : blackcat
     * @date  : 2020/8/7 9:13
    */
    @UpperCaseName("ORGANISM")
    @Abbreviation("Ogm")
    @Name("Organism")
    public interface Organism {
    }
    
    /**
     * <p> 描述 : 植物
     * @author : blackcat
     * @date  : 2020/8/7 9:14
     *
     * Inherited只作用于子类与父类之间的继承
     * Plant接口继承Organism接口,在Organism接口上标记的注解,Plant获取不到
    */
    public interface Plant extends Organism{
    }
    
    /**
     * <p> 描述 : 花
     * @author : blackcat
     * @date  : 2020/8/7 9:14
    */
    @UpperCaseName("FLOWER")
    @Abbreviation("Flr")
    @Name("Flower")
    public abstract class Flower implements Plant {
    }
    
    /**
     * <p> 描述 : 玫瑰
     * @author : blackcat
     * @date  : 2020/8/7 9:15
     *
     * Rose类继承Flower抽象类,在Flower抽象类上标记的注解,如果注解是被@Inherited标记的,都可以获取到
    */
    public class Rose extends Flower {
    }
    
    /**
     * <p> 描述 : 动物
     * @author : blackcat
     * @date  : 2020/8/7 9:15
    */
    @UpperCaseName("ANIMAL")
    @Abbreviation("Ani")
    @Name("Animal")
    public interface Animal extends Organism{
    }
    
    /**
     * <p> 描述 : 哺乳动物
     * @author : blackcat
     * @date  : 2020/8/7 9:15
     *
     * Mamanl抽象类实现Animal接口,在Animal接口上标记的注解,Mammal获取不到
    */
    public abstract class Mammal implements Animal {
    }
    
    /**
     * <p> 描述 : 猴子
     * @author : blackcat
     * @date  : 2020/8/7 9:16
    */
    @UpperCaseName("MONKEY")
    @Abbreviation("Mky")
    @Name("Monkey")
    public class Monkey extends Mammal{
    }
    
    /**
     * <p> 描述 : 金丝猴
     * @author : blackcat
     * @date  : 2020/8/7 9:16
     *
     * Roxellanae类继承Monkey类,在Monkey抽象类上标记的注解,如果注解是被@Inherited标记的,都可以获取到;
     * 如果被@Inherited标记的注解父类和子类重复标记,则返回子类的注解
    */
    @Name("Roxellanae")
    public class Roxellanae extends Monkey {
    }
    

    测试:

    package com.blackcat.metaannotation.inherited;
    
    import java.lang.annotation.Annotation;
    
    /**
     * <p> 描述 : 测试
     * @author : blackcat
     * @date  : 2020/8/7 9:17
     *
     * 说明:
     *    01 Abbreviation 注解标记 @Inherited
     *    02 Name 注解标记 @Inherited
     *    03 UpperCaseName 注解 没有标记 @Inherited
     *
     *  Organism(接口 01,02,03) <- Plant(接口) <- Flower(抽象类 01,02,03) <- Rose(类)
     *  Organism(接口 01,02,03) <- Animal(接口) <- Mammal(抽象类) <- Monkey(类 01,02,03) <- Roxellanae(类 02)
     *
     * 结论:
     *     没有被@Inherited注解标记的注解,如:@UpperCaseName注解,就不具有继承特性,在子类中获取不到父类的注解。
     *
     *     Inherited注解继承概念跟我们理解的面向对象继承概念不一样,它只作用于子类与父类之间的继承。
     *     如:Rose类就从Flower父类中继承了@Abbreviation和@Name注解;对于接口之间的继承和类与接口之间的实现,这两种继承关系不起作用。
     *     如:Plant接口继承Organism接口、Mamanl类实现Animal接口,就没能继承@Abbreviation和@Name注解。
     *
     *     Inherited注解标记的注解,在使用时,如果父类和子类都使用的注解是同一个,那么子类的注解会覆盖父类的注解。
     *     如:Roxellanae类用@Name注解标记了,Monkey类也用@Name注解标记了,那么Roxellanae类注解,会覆盖Monkey的@Name注解。
    */
    public class TestInheritedAnnotation {
    
        public static void main(String[] args){
    
            Annotation[] annotations = Plant.class.getAnnotations();
            System.out.print("Plant接口继承Organism接口,在Organism接口上标记的注解,Plant获取不到:");
            for (Annotation annotation : annotations){
                System.out.print(annotation.toString()+" ");
            }
            annotations = Mammal.class.getAnnotations();
            System.out.print("
    Mamanl抽象类实现Animal接口,在Animal接口上标记的注解,Mammal获取不到:");
            for (Annotation annotation : annotations){
                System.out.print(annotation.toString()+" ");
            }
            annotations = Rose.class.getAnnotations();
            System.out.print("
    Rose类继承Flower抽象类,在Flower抽象类上标记的注解,如果注解是被@Inherited标记的,都可以获取到:");
            for (Annotation annotation : annotations){
                System.out.print(annotation.toString()+" ");
            }
            annotations = Roxellanae.class.getAnnotations();
            System.out.print("
    Roxellanae类继承Monkey类,在Monkey抽象类上标记的注解,如果注解是被@Inherited标记的,都可以获取到;如果被@Inherited标记的注解父类和子类重复标记,则返回子类的注解:");
            for (Annotation annotation : annotations){
                System.out.print(annotation.toString()+" ");
            }
    
        }
    }
    

    输出结果:

    Plant接口继承Organism接口,在Organism接口上标记的注解,Plant获取不到:
    Mamanl抽象类实现Animal接口,在Animal接口上标记的注解,Mammal获取不到:
    Rose类继承Flower抽象类,在Flower抽象类上标记的注解,如果注解是被@Inherited标记的,都可以获取到:@com.blackcat.metaannotation.inherited.annotation.Abbreviation(value=Flr) @com.blackcat.metaannotation.inherited.annotation.Name(value=Flower) 
    Roxellanae类继承Monkey类,在Monkey抽象类上标记的注解,如果注解是被@Inherited标记的,都可以获取到;如果被@Inherited标记的注解父类和子类重复标记,则返回子类的注解:@com.blackcat.metaannotation.inherited.annotation.Abbreviation(value=Mky) @com.blackcat.metaannotation.inherited.annotation.Name(value=Roxellanae)
    

    @Repeatable

    Java 8 新增加的,它允许在相同的程序元素中重复注解,在需要对同一种注解多次使用时,往往需要借助 @Repeatable 注解。

    Java 8 版本以前,同一个程序元素前最多只能有一个相同类型的注解,如果需要在同一个元素前使用多个相同类型的注解,则必须使用注解@Component

    /**
     * <p> 描述 : 声明一个重复注解类
     * @author : blackcat
     * @date  : 2020/8/7 10:38
     *
     * 创建重复注解 Schedule 时加上了 @Repeatable 注解,指向存储注解 Schedules,
     * 这样在使用时就可以直接重复使用 Schedule 注解。
    */
    @Repeatable(Schedules.class)
    public @interface Schedule {
        String dayOfMonth() default "1月";
        String dayOfWeek() default "1周";
        int hour() default 12;
    }
    
    /**
     * <p> 描述 : 声明一个容器注解类
     * @author : blackcat
     * @date  : 2020/8/7 10:38
    */
    @Retention(RetentionPolicy.RUNTIME)
    public @interface Schedules {
        Schedule[] value();
    }
    
    package com.blackcat.metaannotation.repeatable;
    
    import java.lang.reflect.Method;
    
    /**
     * <p> 描述 : 测试
     * @author : blackcat
     * @date  : 2020/8/7 10:38
    */
    @Schedule(dayOfMonth="2月")
    @Schedule(dayOfWeek="2周", hour=24)
    public class TestRepetableAnnotation {
    
        @Schedule(dayOfMonth="3月")
        @Schedule(dayOfWeek="3周", hour=23)
        public void doPeriodicCleanup(){}
    
        public static void main(String[] args) throws NoSuchMethodException {
            Method doPeriodicCleanup = TestRepetableAnnotation.class.getMethod("doPeriodicCleanup");
    
            Schedules schedules = doPeriodicCleanup.getAnnotation(Schedules.class);
            System.out.println("获取标记方法上的重复注解:");
            for (Schedule schedule: schedules.value()){
                System.out.println(schedule);
            }
    
            System.out.println("获取标记类上的重复注解:");
            if (TestRepetableAnnotation.class.isAnnotationPresent(Schedules.class)){
                schedules = TestRepetableAnnotation.class.getAnnotation(Schedules.class);
                for (Schedule schedule: schedules.value()){
                    System.out.println(schedule);
                }
            }
        }
    }
    

    打印结果:

    获取标记方法上的重复注解:
    @com.blackcat.metaannotation.repeatable.Schedule(hour=12, dayOfMonth=3月, dayOfWeek=1周)
    @com.blackcat.metaannotation.repeatable.Schedule(hour=23, dayOfMonth=1月, dayOfWeek=3周)
    获取标记类上的重复注解:
    @com.blackcat.metaannotation.repeatable.Schedule(hour=12, dayOfMonth=2月, dayOfWeek=1周)
    @com.blackcat.metaannotation.repeatable.Schedule(hour=24, dayOfMonth=1月, dayOfWeek=2周)
    

    打印结果说明:

      示例:方法上的@Schedule(dayOfMonth="3月")。其中dayOfMonth="3月",而hourdayOfWeek则为@Schedule注解里的默认值。

    其他打印结果类似。

  • 相关阅读:
    k8s环境搭建--基于minik8s方法
    k8s环境搭建--基于kubeadm方法
    Ubuntu 搭建简单的git server
    国密算法--Openssl 实现国密算法(加密和解密)
    国密算法--Openssl 实现国密算法(基础介绍和产生秘钥对)
    Hyperledger Fabric 1.1 -- Policy 构成
    Pycharm 2018.2.1-2018.1
    6-5 链式表操作集
    6-4 链式表的按序号查找
    6-3 求链式表的表长
  • 原文地址:https://www.cnblogs.com/Kylin-lawliet/p/13446781.html
Copyright © 2020-2023  润新知