Java注解总结

注解

创建注解

下面代码创建了一个名为MyAnno的注解,有两个成员str和val。@Retention(…)注解是为了在运行时通过反射获取该注解,现在可以忽略,后文会详细阐述。

@Retention(RetentionPolicy.RUNTIME)
@interface MyAnno {
    String str();
    int val();
}

首先,注意关键字interface前面的@,这告诉编译器正在声明一种注解类型。接下来注意两个成员都只包含方法声明。但是,不能为这些方法提供方法体,这是由Java实现这些方法。此外,正如后面即将看到的,这些方法的行为更像是域变量。

注解不能包含extends子句。但是,所有注解类型都自动扩展了Annotation接口。因此Annotation是所有注解的超接口。但是该接口实在java.lang.annotation中声明的,其中重写了hashCode()、equals()以及toString()方法,这些方法是由Object方法定义的,另外还制定了annotationType()方法,该方法返回表示调用注解的Class对象。

指定保留策略

保留策略决定了在什么位子丢弃注解。Java定义了三种策略,他们被封装到java.lang.annotation.RetentionPolicy枚举中。这三种策略分别是SOURCE/CLASS/RUNTIME。

  • @Retention(RetentionPolicy.SOURCE):仅存在于源码中,在class字节码文件中不包含。
  • @Retention(RetentionPolicy.CLASS):默认的保留策略,注解编译后存在于class字节码文件中,但运行时不能获取。
  • @Retention(RetentionPolicy.RUNTIME):注解存在于class字节码文件中,运行时可以获取。

注意:局部变量声明的注解不能存储在.class文件中。

在运行时通过反射获取注解

下面声明了一个方法myMeth(),并且用@MyAnno(str="my annotation", val=100)修饰。main方法用反射获取myMeth()方法上@MyAnno注解,并且在控制台输出其两个成员。代码中getAnnotation()方法用来获取指定类型的注解。

测试代码

public class MyAnnoTest {

    @MyAnno(str="my annotation", val=100)
    public void myMeth(){

    }

    public static void main(String[] args) {
        Class<?> clazz = MyAnnoTest.class;
        Method method;
        try {
            method = clazz.getMethod("myMeth");
            MyAnno myAnno= method.getAnnotation(MyAnno.class);
            System.out.println(myAnno.str()+" "+myAnno.val());
        } catch (NoSuchMethodException | NullPointerException e) {
            e.printStackTrace();
        }
    }

}

测试结果

my annotation 100

Process finished with exit code 0

AnnotatedElement接口

AnnotatedElement接口在java.lang.reflect中定义,这个接口支持注解反射,并且Method/Field/Constructor/Class/Package都实现了该接口。

AnnotatedElement接口中定义了一系列操作注解的方法,如下图所示:

AnnotatedElement
  • getAnnotation

    如果存在这样的注解,则返回该元素的指定类型的注解,否则返回null。

  • getAnnotations

    返回此元素上存在的注解。如果此元素上没有注解,则返回值为长度为0的数组。此方法的调用者可以自由修改返回的数组;它将对返回给其他调用者的数组没有影响。

  • getAnnotationsByType

    返回与此元素相关联的注解。如果没有与此元素相关联的注解,则返回值为长度为0.的数组。此方法与getAnnotation(Class)之间的区别在于此方法检测其参数是否为可重复注解类型(JLS 9.6),如果因此,尝试通过“查看”容器注释来查找该类型的一个或多个注解。该方法的调用者可以自由修改返回的数组;它将对返回给其他调用者的数组没有影响。

  • getDeclaredAnnotation

    如果这样的注解直接存在,则返回指定类型的元素注解,否则返回null。此方法忽略继承的注解。 (如果此元素上没有注解,则返回null)

  • getDeclaredAnnotations

    返回直接存在于此元素上的注解。此方法忽略继承的注解。如果此元素上没有直接显示的注解,则返回值为长度为0的数组。此方法的调用者可以自由修改返回的数组;它将对返回给其他调用者的数组没有影响。

  • getDeclaredAnnotationsByType

    如果此类注解直接存在或间接存在,则返回该元素的注解(指定类型)。此方法忽略继承的注解。如果在该元素上没有直接或间接存在的指定注解,则返回值为长度为0.的数组。此方法与getDeclaredAnnotation(Class)之间的区别在于该方法检测其参数是否是可重复的注解类型(JLS 9.6 ),如果是,尝试通过“查看”容器注解(如果存在)来查找该类型的一个或多个注解。该方法的调用者可以自由修改返回的数组;它将对返回给其他调用者的数组没有影响。

  • boolean isAnnotationPresent(Class<? extends Annotation> annoType>)

    判断指定的注解是否和调用对象相关联。

测试代码:

下面的代码片段创建了AnnotatedElementTest的父类AnnotatedElementTestSuper,其被@SheAnno修饰

@SheAnno(name="kylin")
public class AnnotatedElementTestSuper {
}

下面的代码片段创建了注解@MyAnno,并且有两个成员str()和val(),被@Retention(RetentionPolicy.RUNTIME)修饰,说明其可以在运行时获取。

@Retention(RetentionPolicy.RUNTIME)
@interface MyAnno {
    String str();
    int val();
}

下面的代码创建了注解@Retention(RetentionPolicy.RUNTIME),拥有成员name(),可以在运行时获取。@Inherited注解说明其可以被子类继承,即@MyAnno注解修饰的父类A的子类B会继承@MyAnno注解。更多关于@Inherited参见后文。

@Inherited
@Retention(RetentionPolicy.RUNTIME)
public @interface SheAnno {
    String name();
}

测试类,下面的代码主要测试了以下四个方法:

  • getDeclaredAnnotations()
  • getAnnotations
  • isAnnotationPresent
  • getAnnotationsByType

第一个和第二个方法,为了比较有无Declared的区别,第三个判断一个注解是否和调用对象相关,第三个参见后文的重复注解。

上面7个方法中,还有未测试的方法,建议读者自行测试,对比其不同的地方,有助于加深理解AnnotatedAnnotation接口。

测试AnnotatedAnnotation接口:

@MyAnno(str = "string", val = 100)
public class AnnotatedElementTest extends  AnnotatedElementTestSuper{

    public static void main(String[] args) {
        Class<?> clazz = AnnotatedElementTest.class;
        /*
         * 获取声明的注解
         */
        System.out.println("get declared annotations");
        Annotation[] declaredAnnotations = clazz.getDeclaredAnnotations();
        for (Annotation a :
                declaredAnnotations) {
            System.out.println(a);
        }

        /*
         * 获取相关的所有注解,包括继承下来的(与getDeclaredAnnotations()不同之处)。
         * 能继承的类上面的注解,需用@Inherited修饰
         */
        System.out.println("get all annotations");
        Annotation[] allAnnotations = clazz.getAnnotations();
        for (Annotation a :
                allAnnotations) {
            System.out.println(a);
        }

        /*
         * 判断注解是否与类相关
         */
        boolean isAnnotationPre = clazz.isAnnotationPresent(MyAnno.class);
        System.out.println("Annotation MyAnno is present at Class AnnotatedElementTest? " + isAnnotationPre);

        isAnnotationPre = clazz.isAnnotationPresent(SheAnno.class);
        System.out.println("Annotation SheAnno is present at Class AnnotatedElementTest? " + isAnnotationPre);

        /*
         *根据类型获取注解
         */
        System.out.println("get annotation by type");
        Annotation[] annotations = clazz.getAnnotationsByType(MyAnno.class);
        for (Annotation a :
                annotations) {
            System.out.println(a);
        }
    }
}

结果

get declared annotations
@annotation.customize.MyAnno(str=string, val=100)
get all annotations
@annotation.customize.SheAnno(name=kylin)
@annotation.customize.MyAnno(str=string, val=100)
Annotation MyAnno is present at Class AnnotatedElementTest? true
Annotation SheAnno is present at Class AnnotatedElementTest? true
get annotation by type
@annotation.customize.MyAnno(str=string, val=100)

Process finished with exit code 0

使用默认值

如果没有为注解的成员赋值时,则使用默认值。使用default子句声明注解成员的默认值。

声明使用默认值的注解:

@Retention(RetentionPolicy.RUNTIME)
public @interface DefautValAnno {
    String name() default "name";
    int value() default 100;
}

测试:

@DefautValAnno
public class DefautValAnnoTest {
    public static void main(String[] args) {
        Class<?> clazz = DefautValAnnoTest.class;
        DefautValAnno defautValAnno = clazz.getAnnotation(DefautValAnno.class);
        System.out.println(defautValAnno.name() + " " + defautValAnno.value());
    }
}

结果:

name 100

Process finished with exit code 0

标记注解

标记注解是特殊类型的注解,其中不包含成员。标记注解唯一的就是标记声明。因此,这种注解作为注解而存在的理由是充分的。确定标记注解是否存在的最好方式是使用isAnnotationPresent()方法,该方法是有AnnotatedElement接口定义的。

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

单成员注解

单成员注解只包含一个成员。除了允许使用缩写形式指定成员的值之外,单成员注解的工作方式和常规注解类似。如果只有一个成员,应用注解时就可以简单地为该成员指定值,而不需要指定成员的名称。但是,为了使用这种缩写形式,成员名必须是value。

单成员注解的声明:

public @interface SingleParamAnno {
    String value();
    String name() default "aaa";
}

单成员注解的使用:

/**
 * 注解只有一个成员且成员名为value时,使用注解时可直接在括号中填入值。
 * 若其他成员都有默认值,则也可以使用单成员注解。
 */
@SingleParamAnno("siyao")
public class SingleParamAnnoTest {

    @SingleParamAnno(value = "siyao", name = "bbbb")
    public void testMethod() {

    }

}

Java内置注解

Java提供许多内置注解,大部分时专用注解,但是有9个用于一般目的。

元注解

java.lang.annotation包中:

  • @Retention:被设计为只能用于注解其他注解,用来指定保留策略。

    保留策略有三种:

    • @Retention(RetentionPolicy.SOURCE):仅存在于源码中,在class字节码文件中不包含。
    • @Retention(RetentionPolicy.CLASS):默认的保留策略,注解编译后存在于class字节码文件中,但运行时不能获取。
    • @Retention(RetentionPolicy.RUNTIME):注解存在于class字节码文件中,运行时可以获取。
    @Retention(RetentionPolicy.RUNTIME)
    @interface MyAnno {
        String str();
        int val();
    }
    
  • @Documented:注解时标记接口,用于通知某个工具----注解将被文档化,只能注解其他注解。用于指定可以应用注解的类型,被设计为只能注解其他注解。@Target只有一个参数,这个参数必须是来自ElementType常量,这个参数指定了将为其应用注解的声明类型。

    @Documented
    @Retention(RetentionPolicy.RUNTIME)
    public @interface DefautValAnno {
        String name() default "name";
        int value() default 100;
    }
    
  • @Target

    用于指定可以应用注解的类型,被设计为只能注解其他注解。@Target只有一个参数,这个参数必须是来自ElementType常量,这个参数指定了将为其应用注解的声明类型。

    目标常量 可应用注解的声明类型
    ANNOTATION_TYPE 另外一个注解
    CONSTRUCTOR 构造函数
    FIELD 域变量
    LOCAL_VARIABLE 局部变量
    METHOD 方法
    PACKAGE
    PARAMETER 参数
    TYPE 类、接口或枚举
    TYPE_PARAMETER 类型参数
    TYPE_USE 类型使用
    @Target({ElementType.TYPE, ElementType.METHOD})
    @Retention(RetentionPolicy.RUNTIME)
    public @interface MakerAnno {
    }
    
  • @Inherited

    是标记注解,只能用于另外一个注解声明。@Inherited会导致超类的注解被子类继承。

    继承各种情况的详细说明:

    http://elf8848.iteye.com/blog/1621392

    @Inherited
    @Retention(RetentionPolicy.RUNTIME)
    public @interface SheAnno {
        String name();
    }
    

其他内置注解

java.lang包中:

  • @Override

    标记注解,只能用于方法。使用带有@Override注解的方法必须重写超类中的方法。如果不这样,则会出现编译错误。@Override可以确保方法被正真重写。

    示例:

    @Override
    public void run() {
        try {
            for (int i = 0; i < 5; i++) {
                System.out.println("thread: "+name+"time: "+i);
                Thread.sleep(500);
            }
        } catch (InterruptedException e) {
            System.out.println("thread: "+name+" is interrupted");
        }
    
        System.out.println("thread: "+name+" is exiting");
    }
    
  • @Deprecated

    标记注解,用于指示声明是过时的,并且已经被新的形势取代。

    示例:

    @Deprecated
    public void deprecatedMethod(){
    
    }
    
  • @FunctionalInterface

    标记注解,用于接口。指出被注解的接口是一个函数式接口。函数式接口是指仅包含一个抽象方法的接口,由lambda表达式使用。如果被注解的接口不是函数式接口,将报告编译错误。创建函数式接口并不需要使用此注解,使用注解仅仅是在于提供信息。

    示例:

    @FunctionalInterface
    Interface FI{
       abstract judge(int a);
       abstract equals();      
    }
    
  • @SafeVarargs

    标记注解,只能用于方法和构造函数,指示没有发生与可变长度参数相关的不安全动作。如果不安全代码不能具体化的varargs类型相关,或者与参数化的数组实例相关,那么@SafeVarargs注解用于抑制“未检查不安全代码”警告。@SafeVarargs只能用于varargs方法或者声明为static 或final的构造函数。

    示例:

    @SafeVarargs  
    public static <T> T useVarargs(T... args) {  
        return args.length > 0 ? args[0] : null;  
    } 
    

    详情戳下面的链接:

    http://book.51cto.com/art/201205/339154.htm

  • @SuppressWarning

    注解用于指定能抑制一个或多个编译器可能会报告的警告。使用以字符串形式表示的名称来指定要被抑制的警告。

类型注解

从JDK 8开始,Java增加了可以使用注解的地方。最早的注解只能应用于声明。但是,在JDK 8中,在能够使用类型的大多数地方,也可以指定注解。扩展后的这种注解称为类型注解。例如,可以注解方法的返回类型、方法内this类型、强制转换、数组级别、被继承的类以及throws子句。可以注解泛型,包括泛型参数边界和泛型类型参数。

类型注解很重要,因为他们允许工具对代码执行额外的检查,从而帮助避免错误。需要理解,javac本身一般不执行这些检查,所以需要使用单独的工具,不过这种工具可能需要作为编译器插件发挥作用。

类型注解必须包含ElementType.TYPE_USE作为目标,用@Target指定。类型注解需要放到应用该注解的类型的前面。

下面的代码,详细列出了类型注解的使用方式:

public class TypeAnnoTest <@TypeAnno("type parameter") T> {

    //use a type annotation on a field
    @TypeAnno("field") String string;

    //use a type annotation on a constructor
    public @TypeAnno("unique") TypeAnnoTest(){

    }

    //use a type annotation to annotate this
    public void method (@TypeAnno("this") TypeAnnoTest<T> this, int a, String str){

    }

    //annotate return type
    public @TypeAnno("return") int method2(){
        return 1;
    }

    //use a type annotation with a throws clause
    public void meghod3() throws @TypeAnno("throws") NullPointerException{

    }

    //annotate the array element type
    @TypeAnno("array") String [] strings;

    public void method4(){
        //use a type annotation on a type argument
        TypeAnnoTest<@TypeAnno("argument") Integer> ob = new TypeAnnoTest<>();

        //use a type annotation with new
        @TypeAnno("new") TypeAnnoTest<String> ob2 = new TypeAnnoTest<>();

        //use a type annotation on a cast
        Object x  = new Integer(10);
        Integer y = (@TypeAnno("cast") Integer) x;
    }
}

//use type annotation with inheritance clause
class TypeAnnoTestSub extends @TypeAnno("inherit") TypeAnnoTest<String>{

}

重复注解

JDK 8中新增的另外一个特性允许在元素上重复应用注解,这种特性成为重复注解。可重复注解必须用@Repeatable进行注解。@Repeatable注解定义在java.lang.annotation中,其value域指定了重复注解的容器类型。容器被指定为注解,对于这种注解,value域是重复注解类型的数组。因此,要创建重复注解,必须创建容器注解。

声明重复注解

@Repeatable(RepeatableAnnoContainer.class)
@Retention(RetentionPolicy.RUNTIME)
@interface RepeatableAnno {
    String name();
    int value();
}

声明重复注解的容器注解

@Retention(RetentionPolicy.RUNTIME)
public @interface RepeatableAnnoContainer {
    RepeatableAnno[] value();
}

测试代码

public class Test {

    @RepeatableAnno(name = "kylin", value = 100)
    @RepeatableAnno(name = "siyao", value = 200)
    public void testMethod() {

    }

    public static void main(String[] args) {
        Class<?> clazz = Test.class;
        try {
            Method method = clazz.getMethod("testMethod");
            //使用getAnnotations()获取重复注解
            Annotation[] getAnnotations = method.getAnnotations();
            for (Annotation a :
                    getAnnotations) {
                System.out.println(a);
            }
            //使用getAnnotationsByType()获取重复注解
            Annotation[] annotations = method.getAnnotationsByType(RepeatableAnnoContainer.class);
            for (Annotation a :
                    annotations) {
                System.out.println(a);
            }
        } catch (NoSuchMethodException e) {
            System.out.println("no such method");
        }


    }
}

结果

@annotation.repeatable.RepeatableAnnoContainer(value=[@annotation.repeatable.RepeatableAnno(name=kylin, value=100), @annotation.repeatable.RepeatableAnno(name=siyao, value=200)])
@annotation.repeatable.RepeatableAnnoContainer(value=[@annotation.repeatable.RepeatableAnno(name=kylin, value=100), @annotation.repeatable.RepeatableAnno(name=siyao, value=200)])

Process finished with exit code 0

一些限制

使用注解声明有许多限制。首先,一个注解不能继承另一个注解。其次,注解声明的所有方法都必须不带参数。此外,他们不能返回一下类型的值:

  • 基本类型,例如int或double
  • String或Class类型的对象
  • 枚举类型
  • 其他注解类型
  • 上述类型的数组

注解不能泛型化。换句话说,他们不能带有类型番薯。最后注解方法不能指定throw子句。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,547评论 6 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,399评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,428评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,599评论 1 274
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,612评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,577评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,941评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,603评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,852评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,605评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,693评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,375评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,955评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,936评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,172评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,970评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,414评论 2 342

推荐阅读更多精彩内容