Java 注解 Annotation

What

Annotation 是何物 ? ** @Override **相信 Java 程序员都不陌生,经常会在一些方法上看到这样一个符号,这个 ** @Override **便是一个Annotation。如:

    @Override
    public String toString() {
        return super.toString();
    }

Annotation:中文译之为注解,是一种能够添加到 Java 源代码中的语法元数据,于 Java SE5中被引入。注解可用来将信息元数据与程序元素进行关联,可以作用于包、类、接口、方法、域、参数、注解之上。注解为我们在代码中添加信息提供了一种形式化的方法,使我们可以在稍后某个时刻非常方便的使用这些数据。

Why

注解在一定程度上是在将元数据与源代码文件结合在一起,而不是保存在外部文档中的大趋势下诞生的。注解的优点主要有:

  • 注解可以提供用来完整地描述程序所需要的信息;
  • 注解可以将一些数据、格式的验证和测试工作抽取出来交由编译器;
  • 注解可以用来生成描述符文件、新的类定义文件或者一些“样板”代码,有助于减轻编写“样板”代码的负担;
  • 使用注解从某种程度上可以提高代码的可读性;

注解分类

元注解:即专职用于创建其它注解的注解,Java提供了四种元注解:@Target、@Retention、@Documented 和 @ Inherited

@Target:该注解用于定义注解的作用域,值可以取枚举类型 ElementType 中的一个或多个,如果未指定Target值,则该注解可以作用于如下所有的域。

  • TYPE:类/接口/枚举/Annotation 声明
  • FIELD:域/枚举常量声明
  • METHOD:方法声明
  • PARAMETER:参数声明
  • CONSTRUCTOR:构造函数声明
  • LOCAL_VARIABLE:局部变量声明
  • ANNOTATION_TYPE:Annotation 类型声明
  • PACKAGE:包声明

@Retention:该注解表示需要在什么级别保存该注解信息,可选值为枚举类型 RetentionPolicy 中的一个:

  • SOURCE:注解将被编译器丢弃,主要起** 标记 **作用,告诉编译器一些信息,如@Override 和 @SuppressWarnings;
  • CLASS:注解在 class 文件中可用,但是会被 JVM 丢弃,可用于编译时动态处理,如动态生成代码;
  • RUNTIME:注解将会一直保留,因此可以通过反射机制读取注解信息,@Deprecated 和 @SafeVarargs;

@Documented :将此注解保留在 Javadoc 中;
@Inherited:允许子类继承父类中的注解。

标准注解:Java内置了四种标准注解:@Override、@SuppressWarnings、@Deprecated 和 @SafeVarargs
@Override:表示当前的方法定义将会覆盖父类中的方法,如果当前方法签名与父类中的方法签名不一致,编译器将会发出错误提示信息;
@SuppressWarnings:关闭不当的编译器警告信息;
@Deprecated :表示该方法已经被弃用,如果程序中使用到被该注解作用的方法,编译器会发出警告信息;
@SafeVarargs:该注解是 Java 1.7 新引入的一个注解,表示该注解作用的对象不会对其可变参数进行任何潜在不安全的修改

自定义注解:Java提供的注解数目相对比较少,不过我们可以通过自定义注解的方式创建符合自己程序需要的注解类型。

How

这部分主要介绍如何创建一个新的注解并根据程序需要获取注解信息进行处理。

定义注解

自定义注解之前,先看看 Java 标准注解是怎样定义的,从而对注解有个大致印象!
下面两段代码分别是 @Override 和 @SuppressWarnings 注解的定义。

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.SOURCE)    //使用元注解设置注解作用域、保留策略等
public @interface Override {
}
@Target( { ElementType.TYPE, ElementType.FIELD, ElementType.METHOD,
        ElementType.PARAMETER, ElementType.CONSTRUCTOR,
        ElementType.LOCAL_VARIABLE })
@Retention(RetentionPolicy.SOURCE)
public @interface SuppressWarnings {
    public String[] value();      // 注解配置信息
}

从上面两个标准注解定义中可以看出,注解的定义有点类似于接口定义,注解也会被编译成 .class 文件。

** 定义注解的步骤:**

  1. 使用** @interface ** 就可以定义一个注解,注解名紧随其后;
  2. 然后根据程序需要,使用 Java 元注解设置注解的作用域、保留级别、是否可继承等信息;
  3. 根据需要,在注解中包含一些元素用于表示一些配置参数,提供必要的注解信息以便于后续对注解进行分析处理。

** 实例 **

@Target(ElementType.METHOD)              // 该注解可以作用于方法声明
@Retention(RetentionPolicy.RUNTIME)     // 该注解会一直保留到运行时
@Documented                            // 将注解信息保留到 Javadoc中               
@Inherited                            // 该注解可以被子类继承
public @interface Property {
     int id();                          // int 类型元素 id
     String msg() default "default msg";  // 默认值为 “default msg” 的 String 类型元素 msg
//   Boolean flag() default false;    // Invalide type 'Boolean' for annotaton member
}

上面一段代码自定义了一个名为 Property 的注解,该注解可以作用于方法声明,内部包含两个配置参数 id 和 msg,使用注解时可通过这两个元素传递注解信息。

注解元素

注解中包含的方法声明即为注解元素,注解元素定义类似于接口中的方法定义,元素的类型即为所需参数的类型,如 int id()表示需要一个int类型值,不过注解元素可以通过使用default关键字定义默认值,如果在使用注解时,没有为设置了默认值的元素提供值,则会使用该元素的默认值。不包含注解元素的注解为** 标记注解 **,这类注解的存在只是为了提供某种标记信息,如@Override注解。

注解元素只能使用如下几种类型,使用其余类型时,编译器会给出错误提示:

  • 所有基本类型(int、char、double等)
  • String
  • Class
  • enum
  • Annotation(即嵌套注解)
  • 以上类型的数组(注意只能是数组,而不能是容器类型)

使用注解

public class Goods {

    @Property(id = 1, msg = "per price")
    public void price(){}

    @Property(id = 2)
    public void type(){}
}

注解通过名值对的形式赋值,具有默认值的注解可以不赋值,从而使用默认值。上面这段代码在 Goods 类中创建了两个函数 price() 和 type(),这两个函数均使用了自定义的 Property 注解,price() 函数为配置参数 id 和 msg 都赋了值,而type()函数只设置了 id 的值,这意味着type()函数的 msg 为默认值“default msg”。

编写注解处理器

至此已经了解了如何创建和使用注解,然而如果没有注解处理器对注解信息进行加工处理的话,注解也就不能显示其强大功能了。Java SE5扩展了反射机制的 API,以帮助程序员构建这类工具。同时,还提供了一个外部工具 apt(Annotation Process Tool)帮助解析带有注解的 Java源码。所有注解均可以使用Mirror API 在编译时便获取类型信息,但只有@Retention 为 RetentionPolicy.RUNTIME 的注解可以在运行时使用Java反射机制获取数据。

下面代码是一个简单的注解处理器,用于读取上面的 Goods 类,并使用反射机制在运行时查找我们自定义的注解Property,通过Property注解获取方法的 id 和 msg。

public class PropertyTracker {

    public static void trackerProperty(List<Integer> ids, Class cls){
        for (Method method : cls.getDeclaredMethods()){
            Property annot = method.getAnnotation(Property.class);
            if (annot != null){
                print("The Annotation's id is: " + annot.id() + " msg is: " + annot.msg());
                ids.remove(new Integer(annot.id()));
            }else {
                print("This method doesn't has Annotation - Property!");
            }
        }

        for (Integer i : ids){
            print("Warning! Missing Property - " + i);
        }
    }

    public static void main(String[] args) {
        List<Integer> propertys = new ArrayList<>(5);
        Collections.addAll(propertys, 1, 2, 3, 4, 5);
        trackerProperty(propertys, Goods.class);
    }
}
//Output:
The Annotation's id is: 2 msg is: default msg
The Annotation's id is: 1 msg is: per price
Warning! Missing Property - 3
Warning! Missing Property - 4
Warning! Missing Property - 5

解析:
trackerProperty方法需要一个List 类型参数 ids 和 一个Class类型参数cls,然后它会找出cls中有的Property和缺失的Property。
trackerProperty方法中使用到了两个反射方法:
getDeclaredMethods():返回一个包含该类中定义的所有方法的数组
getAnnotation():返回实参指定类型的Annotation,如果调用者没有该类型的注解则返回null值。
通过使用注解对象 annot 的id()和msg()方法获取注解的id和msg信息。

快速赋值机制

当注解内部只有一个需要赋值的注解元素时,可以将该注解元素名设为value,从而提供一种快捷赋值方式。即使用该注解时,直接在括号内提供注解元素的值即可,而无需使用名值对的方式。
如下所示:

@Target({ElementType.METHOD, ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
public @interface Person {
    int value();
    String sex() default "girl";
    boolean student() default true;
}
public class Chatlist {

    @Person(1)
    String per1 = "person1";

    @Person(value = 2, sex = "boy", student = false)
    String per2 = "person2";
}

注解Person内部包含三个注解元素:value、sex和student,其中sex和student均有默认值,所以value是唯一需要赋值的注解元素,可以使用快速赋值机制。不过当需要赋值的元素不止一个时,则value也需要使用名值对的方式赋值,如per2所示。

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

推荐阅读更多精彩内容