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以外的任何地方。

/**
 *  描述 : 使用TYPE_PARAMETER类型,该注解只能在类型参数声明时使用
 * @author : blackcat
 * @date  : 2020/8/6 15:04
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE_PARAMETER)
public @interface TypeParammeterAnnotation {
}
/**
 *  描述 : 使用TYPE_USE类型,该注解能标注任何类型名称
 * @author : blackcat
 * @date  : 2020/8/6 15:38
*/
@Target(ElementType.TYPE_USE)
@Retention(RetentionPolicy.RUNTIME)
public @interface TypeUseAnnotation {

}
/**
 *  描述 : 泛型类型声明时,使用使用TYPE_PARAMETER类型,编译通过
 * @author : blackcat
 * @date  : 2020/8/6 15:05
*/
public class TestTypeParameter {
    /** 使用TYPE_PARAMETER类型,会编译不通过 */
    /*public @TypeParammeterAnnotation T test(@TypeParammeterAnnotation T a){
        new ArrayList();
            return a;
    }*/
}
/**
 *  描述 : ElementType.TYPE_USE使用示例
 *         使用TYPE_USE类型,该注解能标注任何类型名称
 * @author : blackcat
 * @date  : 2020/8/6 15:33
*/
public class TestTypeUse {

    /** 泛型类型声明时,使用TYPE_USE类型,编译通过 */
    public static @TypeUseAnnotation class TypeUseClass 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 typeUseClass = new @TypeUseAnnotation TypeUseClass<>();
        typeUseClass.foo("");
        List list1 = new ArrayList<>();
        List list2 = new ArrayList<>();
        @TypeUseAnnotation String text = (@TypeUseAnnotation String)new Object();
        java.util. @TypeUseAnnotation Scanner console = new java.util.@TypeUseAnnotation Scanner(System.in);
    }
}
/**
 *  描述 : ElementType.METHOD只能修饰方法。
 * @author : blackcat
 * @date  : 2020/8/6 16:37
*/
@Target(ElementType.METHOD)
public @interface MethodAnnotation {
}
/**
 *  描述 : 测试
 * @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用来压制程序中出来的警告,比如在没有用泛型或是方法已经过时的时候。

/**
 *  描述 : 示不仅会在编译后的class文件中存在,而且在运行时保留。
 *         因此它们主要用于反射场景,可以通过getAnnotation方法获取。
 * @author : blackcat
 * @date  : 2020/8/6 16:08
*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface Country {
    /** 国家名称 */
    String name();
    /** 国家语言 */
    String[] languages();
}
/**
 *  描述 : 默认策略,表示注解会在编译后的class文件中存在,但是在运行时,不会被VM保留。
 * @author : blackcat
 * @date  : 2020/8/6 16:09
*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.CLASS)
public @interface Region {
    /** 地区名称 */
    String name();
    /** 所属国家 */
    String country();
}
/**
 *  描述 : 表示注解会在编译时被丢弃
 * @author : blackcat
 * @date  : 2020/8/6 16:10
*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.SOURCE)
public @interface Home {
    /** 成员 */
    String[] members();
    /** 地址 */
    String address();
}
/**
*  描述 : 测试
* @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 之类的工具处理,所以注解类型信息就会被包括在生成的帮助文档中。

/**
 *  描述 : 用 @Documented 注解修饰的注解类会被 JavaDoc 工具提取成文档
 * @author : blackcat
 * @date  : 2020/8/6 16:47
*/
@Documented
@Target({ ElementType.TYPE, ElementType.METHOD })
public @interface MyDocumented {
    String value() default "这是@Documented注解";
}
/**
 *  描述 :测试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可以看到以下内容。

Java 元注解 使用示例

@Inherited

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

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

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

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

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

/**
 *  描述 : 有机体
 * @author : blackcat
 * @date  : 2020/8/7 9:13
*/
@UpperCaseName("ORGANISM")
@Abbreviation("Ogm")
@Name("Organism")
public interface Organism {
}
/**
 *  描述 : 植物
 * @author : blackcat
 * @date  : 2020/8/7 9:14
 *
 * Inherited只作用于子类与父类之间的继承
 * Plant接口继承Organism接口,在Organism接口上标记的注解,Plant获取不到
*/
public interface Plant extends Organism{
}
/**
 *  描述 : 花
 * @author : blackcat
 * @date  : 2020/8/7 9:14
*/
@UpperCaseName("FLOWER")
@Abbreviation("Flr")
@Name("Flower")
public abstract class Flower implements Plant {
}
/**
 *  描述 : 玫瑰
 * @author : blackcat
 * @date  : 2020/8/7 9:15
 *
 * Rose类继承Flower抽象类,在Flower抽象类上标记的注解,如果注解是被@Inherited标记的,都可以获取到
*/
public class Rose extends Flower {
}
/**
 *  描述 : 动物
 * @author : blackcat
 * @date  : 2020/8/7 9:15
*/
@UpperCaseName("ANIMAL")
@Abbreviation("Ani")
@Name("Animal")
public interface Animal extends Organism{
}
/**
 *  描述 : 哺乳动物
 * @author : blackcat
 * @date  : 2020/8/7 9:15
 *
 * Mamanl抽象类实现Animal接口,在Animal接口上标记的注解,Mammal获取不到
*/
public abstract class Mammal implements Animal {
}
/**
 *  描述 : 猴子
 * @author : blackcat
 * @date  : 2020/8/7 9:16
*/
@UpperCaseName("MONKEY")
@Abbreviation("Mky")
@Name("Monkey")
public class Monkey extends Mammal{
}
/**
 *  描述 : 金丝猴
 * @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> &#x63CF;&#x8FF0; : &#x6D4B;&#x8BD5;
 * @author : blackcat
 * @date  : 2020/8/7 9:17
 *
 * &#x8BF4;&#x660E;&#xFF1A;
 *    01 Abbreviation &#x6CE8;&#x89E3;&#x6807;&#x8BB0; @Inherited
 *    02 Name &#x6CE8;&#x89E3;&#x6807;&#x8BB0; @Inherited
 *    03 UpperCaseName &#x6CE8;&#x89E3; &#x6CA1;&#x6709;&#x6807;&#x8BB0; @Inherited
 *
 *  Organism(&#x63A5;&#x53E3; 01,02,03) <- plant(接口) <- flower(抽象类 01,02,03) rose(类) * organism(接口 animal(接口) mammal(抽象类) monkey(类 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 { 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()+" "); } system.out.print("\nmamanl抽象类实现animal接口,在animal接口上标记的注解,mammal获取不到:"); system.out.print("\nrose类继承flower抽象类,在flower抽象类上标记的注解,如果注解是被@inherited标记的,都可以获取到:"); system.out.print("\nroxellanae类继承monkey类,在monkey抽象类上标记的注解,如果注解是被@inherited标记的,都可以获取到;如果被@inherited标记的注解父类和子类重复标记,则返回子类的注解:"); < code></-></p>

输出结果:

Plant&#x63A5;&#x53E3;&#x7EE7;&#x627F;Organism&#x63A5;&#x53E3;&#xFF0C;&#x5728;Organism&#x63A5;&#x53E3;&#x4E0A;&#x6807;&#x8BB0;&#x7684;&#x6CE8;&#x89E3;&#xFF0C;Plant&#x83B7;&#x53D6;&#x4E0D;&#x5230;&#xFF1A;
Mamanl&#x62BD;&#x8C61;&#x7C7B;&#x5B9E;&#x73B0;Animal&#x63A5;&#x53E3;&#xFF0C;&#x5728;Animal&#x63A5;&#x53E3;&#x4E0A;&#x6807;&#x8BB0;&#x7684;&#x6CE8;&#x89E3;&#xFF0C;Mammal&#x83B7;&#x53D6;&#x4E0D;&#x5230;&#xFF1A;
Rose&#x7C7B;&#x7EE7;&#x627F;Flower&#x62BD;&#x8C61;&#x7C7B;&#xFF0C;&#x5728;Flower&#x62BD;&#x8C61;&#x7C7B;&#x4E0A;&#x6807;&#x8BB0;&#x7684;&#x6CE8;&#x89E3;&#xFF0C;&#x5982;&#x679C;&#x6CE8;&#x89E3;&#x662F;&#x88AB;@Inherited&#x6807;&#x8BB0;&#x7684;&#xFF0C;&#x90FD;&#x53EF;&#x4EE5;&#x83B7;&#x53D6;&#x5230;&#xFF1A;@com.blackcat.metaannotation.inherited.annotation.Abbreviation(value=Flr) @com.blackcat.metaannotation.inherited.annotation.Name(value=Flower)
Roxellanae&#x7C7B;&#x7EE7;&#x627F;Monkey&#x7C7B;&#xFF0C;&#x5728;Monkey&#x62BD;&#x8C61;&#x7C7B;&#x4E0A;&#x6807;&#x8BB0;&#x7684;&#x6CE8;&#x89E3;&#xFF0C;&#x5982;&#x679C;&#x6CE8;&#x89E3;&#x662F;&#x88AB;@Inherited&#x6807;&#x8BB0;&#x7684;&#xFF0C;&#x90FD;&#x53EF;&#x4EE5;&#x83B7;&#x53D6;&#x5230;&#xFF1B;&#x5982;&#x679C;&#x88AB;@Inherited&#x6807;&#x8BB0;&#x7684;&#x6CE8;&#x89E3;&#x7236;&#x7C7B;&#x548C;&#x5B50;&#x7C7B;&#x91CD;&#x590D;&#x6807;&#x8BB0;&#xFF0C;&#x5219;&#x8FD4;&#x56DE;&#x5B50;&#x7C7B;&#x7684;&#x6CE8;&#x89E3;&#xFF1A;@com.blackcat.metaannotation.inherited.annotation.Abbreviation(value=Mky) @com.blackcat.metaannotation.inherited.annotation.Name(value=Roxellanae)

@Repeatable

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

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

/**
 *  描述 : 声明一个重复注解类
 * @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;
}
/**
 *  描述 : 声明一个容器注解类
 * @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;

/**
 *  描述 : 测试
 * @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&#x6708;")。其中 dayOfMonth="3&#x6708;",而 hourdayOfWeek则为 @Schedule注解里的默认值。

其他打印结果类似。

Original: https://www.cnblogs.com/Kylin-lawliet/p/13446781.html
Author: 黑猫的黑猫黑猫
Title: Java 元注解 使用示例

原创文章受到原创版权保护。转载请注明出处:https://www.johngo689.com/593611/

转载文章受原作者版权保护。转载请注明原作者出处!

(0)

大家都在看

亲爱的 Coder【最近整理,可免费获取】👉 最新必读书单  | 👏 面试题下载  | 🌎 免费的AI知识星球