Java 注解完全解析

无论是做Java后台或者Android开发,我们经常使用注解,最常用的莫过于@Override,很多流程的框架都用到了注解这个功能机制,例如Java后台开发中常用的框架Spring、MyBatis等,Android的Retrofit,Butterknife等,都是注解框架。有关注解也常常在面试中被问到,但我们了解注解的本质是什么?有哪些类别?如何自定义及使用?其中工作原理是什么?本文将做全面介绍。

一、注解的定义

关于注解首先引入官方文档的一句话:Java 注解(Annotation)用于为 Java 代码提供元数据。作为元数据,注解不直接影响你的代码执行,但也有一些类型的注解实际上可以用于这一目的。Java 注解是从 Java5 开始添加到 Java 的。

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

作用:标识 / 解释 Java 代码。

二、注解类型

1、元注解

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

@Retention

@Retention(RetentionPolicy.RUNTIME)//使用该注解,只能选择其中一种属性,不能定义多个
public @interface MyAnnotation {
}

Retention英文意思有保留、保持的意思,它表示注解存在阶段是保留在源码(编译期),字节码(类加载)或者运行期(JVM中运行)。
在@Retention注解中使用枚举RetentionPolicy来表示注解保留时期
Retention策略 :

  • @Retention(RetentionPolicy.SOURCE) ,注解仅存在于源码中,在class字节码文件中不包含
  • @Retention(RetentionPolicy.CLASS), 默认的保留策略,注解会在class字节码文件中存在,但运行时无法获得
  • @Retention(RetentionPolicy.RUNTIME), 注解会在class字节码文件中存在,在运行时可以通过反射获取到

@Target

@Target({ElementType.FIELD,ElementType.METHOD}) //多个的时候使用{}括起来,然后用逗号分隔开
public @interface MyAnnotation {
}

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类型.

@Documented

@Documented //添加文档注解
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface MyAnnotation {
}

Document的英文意思是文档。它的作用是能够将注解中的元素包含到 Javadoc 中去。

@Inherited

@Documented
@Inherited
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface MyAnnotation {
}

Inherited的英文意思是继承,但是这个继承和我们平时理解的继承大同小异,一个被@Inherited注解了的注解修饰了一个父类,则它的子类也继承了父类的注解(子类可以同时被其他注解修饰)。

@MyAnnotation
public class FatherClass {
}

public class SonClass extends FatherClass {
}
 
public class test {
   public static void main(String[] args){
      Class<SonClass> sonClass = SonClass.class;
      MyAnnotation annotation =sonClass.getAnnotation(MyAnnotation.class);
   }
}

上面的例子,父类加了可继承注解,子类没有被该注解修饰,但是子类获取Mynnotation注解成功,说明子类继承了父类中的MyAnnotation。

@Repeatable
Repeatable的英文意思是可重复的。顾名思义说明被这个元注解修饰的注解可以同时作用一个对象多次,但是每次作用注解又可以代表不同的含义。

Java 1.8后引进,使得作用的注解可以取多个值
下面通过例子说明其定义。

// 1. 定义 容器注解 @RoleContainer
//    容器注解本身也是一个注解,用于存放其他注解
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public   @interface RoleContainer {
    Role[] value();
}
 
// 2. 定义@Role 
//   使用@Repeatable 注解 @Role
//   注:@Repeatable 括号中的类 = 容器注解
@Repeatable(RoleContainer.class)
@Retention(RetentionPolicy.RUNTIME)
public  @interface Role {
    String role() default "";
}

// 3. 在使用@Role(被@Repeatable 注解 )时,可以取多个值来解释Java代码
// 下面注解表示:Man有多个身份
@Role(role = "CEO")
@Role(role = "Father")
@Role(role = "husband")
@Role(role = "son")
public class Man {
}

2、Java内置注解

定义:即Java内部已经实现好的注解

Java中 内置的注解有5类,具体包括:

@Deprecated:过时注解,用于标记已过时 & 被抛弃的元素(类、方法等)

@Override:复写注解,用于标记该方法需要被子类复写

@SuppressWarnings:阻止警告注解,用于标记的元素会阻止编译器发出警告提醒

@SafeVarargs:参数安全类型注解,用于提醒开发者不要用参数做不安全的操作 & 阻止编译器产生 unchecked警告,Java 1.7 后引入

// 以下是官方例子
// 虽然编译阶段不报错,但运行时会抛出 ClassCastException 异常
// 所以该注解只是作提示作用,但是实际上还是要开发者自己处理问题
@SafeVarargs // Not actually safe!
    static void m(List<String>... stringLists) {
    Object[] array = stringLists;
    List<Integer> tmpList = Arrays.asList(42);
    array[0] = tmpList; // Semantically invalid, but compiles without warnings
    String s = stringLists[0].get(0); // Oh no, ClassCastException at runtime!
}

@FunctionalInterface:函数式接口注解,数式接口 (Functional Interface) = 1个具有1个方法的普通接口,Java 1.8 后引入的新特性

// 多线程开发中常用的 Runnable 就是一个典型的函数式接口(被 @FunctionalInterface 注解)
@FunctionalInterface
public interface Runnable {
   
    public abstract void run();
}

3、自定义注解

定义:开发者自定义注解
下一节介绍如何自定义注解,及学习注解属性和如何获取注解的属性。

三、注解的使用

1、定义注解

定义注解形式类似接口,只是在interface前面加@:

public  @interface Role {
    String role() default "";
}

2、注解属性

注解的本质就是一个Annotion接口。

/**Annotation接口源码*/
public interface Annotation {

    boolean equals(Object obj);

    int hashCode();

    Class<? extends Annotation> annotationType();
}

通过以上源码,我们知道注解本身就是Annotation接口的子接口,也就是说注解中其实是可以有属性和方法,但是接口中的属性都是static final的,对于注解来说没什么意义,而我们定义接口的方法就相当于注解的属性,也就说为什么注解只有属性成员变量,其实他就是接口的方法,这就是为什么成员变量会有括号,不同于接口我们可以在注解的括号中给成员变量赋值。

public  @interface Role {
    //注解的属性,注解只有属性,没有方法,方法名=属性名,方法返回值 = 属性类型
    String role() default "";
    
    int id();
}

//注解括号内以 属性名=xx 形式赋值, 用,隔开多个属性
Role(role="father" ,id =1)

//若注解只有一个属性,则赋值时”value“可以省略
Role("father")

注解属性类型可以有以下列出的类型:

  • 基本数据类型
  • String
  • 枚举类型
  • 注解类型
  • Class类型
  • 以上类型的一维数组类型

3、使用注解

在类/成员变量/方法定义前 加上 “@注解名” 就可以使用该注解。

//表示Tony的角色是CEO
@Role(role = "CEO")
public class Tony {
}

4、获取注解

前面介绍Java自带的注解和如何自定义注解,如何使用,接下来学习如何获取注解,这是使用注解的关键,而使用注解的目的是为了获取注解属性值。
如何获取注解?当然是Java的反射技术了。

用于Java反射会带来一定的耗时,因此使用运行注解需要考虑对性能的影响。

获取注解的方法:

 /**是否存在对应 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  @interface Role {
    String role() default "";
    int id();
}


@Role(role = "CEO", id = 1)
public class Tony {
    @Role(role = "teacher", id = 2)
    public String son;

    @Role(role = "doctor", id = 3)
    public String getDaughter() {
        return "daughter is doctor";
    }
}

public class TestAnnotation {
   public static void test() {
       //判断是否被注解修饰
        boolean isRoleAnnotation = Tony.class.isAnnotationPresent(Role.class);
        Log.d(TAG, "tony is decorated by RoleAnnotation:" + isRoleAnnotation);

        //获取类注解属性
        Role roleAnnotation = Tony.class.getAnnotation(Role.class);
        if (roleAnnotation != null) {
            Log.d(TAG, "tony role is " + roleAnnotation.role());
        }

        //获取方法注解属性
        Class<Tony> tony = Tony.class;
        try {
            Field field = tony.getField("son");
            roleAnnotation = field.getAnnotation(Role.class);
            if (roleAnnotation != null) {
                Log.d(TAG, "field role is " + roleAnnotation.role());
            }

            Method method = tony.getDeclaredMethod("getDaughter");
            roleAnnotation = field.getAnnotation(Role.class);
            if (roleAnnotation != null) {
                Log.d(TAG, "method role is " + roleAnnotation.role());
            }
        } catch (Exception e) {

        }
    }
}

打印结果:

D/TestAnnotation: tony is decorated by RoleAnnotation:true
D/TestAnnotation: tony role is CEO
D/TestAnnotation: field role is teacher
D/TestAnnotation: method role is teacher

四、注解的应用

按照注解的Retention属性其应用分为3个场景:

  • 提供信息给编译器: 编译器可以利用注解来检测出错误或者警告信息,打印出日志。
  • 编译阶段时的处理: 软件工具可以用来利用注解信息来自动生成代码、文档或者做其它相应的自动处理。
  • 运行时处理: 某些注解可以在程序运行的时候接受代码的提取,自动做相应的操作。

应用举例

应用场景1:测试代码

如出名的测试框架JUnit = 采用注解进行代码测试。

public class ExampleUnitTest {
    @Test
    public void Method() throws Exception {
          ...
    }
}
// @Test 标记了要进行测试的方法Method() 

应用场景2:解耦&简化代码,提高开发效率

Android 开发中大名鼎鼎的 IOC 框架ButterKnife,它减少了大量重复的代码。

public class TestActivity extends AppCompatActivity {
 
    @BindView(R.id.tvTest)
    TextView tvTest;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_test);
 
        ButterKnife.bind(this);
    }

很牛逼的 Http 网络访问框架Retrofit,采用注解描述网络请求参数:

public interface UserService {
   @GET("users/all")
   Call<List<UserInfo>> listUsers();
}
 
Retrofit retrofit = new Retrofit.Builder()
    .baseUrl("https://xxx.com/")
    .build();
 
UserService userService = retrofit.create(UserService.class)
Call<Response<List<UserInfo>>> call = userService.listUsers();

五、注解的工作原理

编译时处理——APT

编译时处理需要使用到APT技术,该技术提供了一套编译期的注解处理流程。

image

在编译期扫描.java文件的注解,并传递到注解处理器,注解处理器可根据注解生成新的.java文件,这些新的.java问和原来的.java一起被javac编译。

image

注解处理器是一个在javac编译期处理注解的工具,你可以创建注解处理器并注册,在编译期你创建的处理器以Java代码作为输入,生成文件.java文件作为输出。注解处理器不能修改已有的Java类,只能生成新的Java类。

运行时处理
通过反射技术处理运行时注解,这里参考一下反射的原理,运行时注解就是通过反射技术获取带有注解修饰的属性、方法。

image

六、总结

注解在我们开发中很常见,会使用注解是我们必须掌握的技能。本文结合例子全面总结了注解的定义、类型,如何自定义及使用注解,注解的工作原理,及应用场景。巧用注解可以提高开发效率,写出优雅的代码,但也要注意到运行注解会带来性能问题,需要我们深入理解注解的原理,权衡使用。

关注V: “码农翻身记”,回复888,免费领取Android/Java高频面试题解析、进阶知识整理、图解网络、图解操作系统等资料。关注后,你将不定期收到优质技术及职场干货分享。

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

推荐阅读更多精彩内容

  • 关于注解首先引入官方文档的一句话:Java 注解用于为 Java 代码提供元数据。作为元数据,注解不直接影响你的代...
    编程小世界阅读 455评论 0 0
  • 一 注解的定义 注解(Annotation),也叫元数据。一种代码级别的说明。它是JDK1.5及以后版本引入的一个...
    _那个人阅读 3,210评论 0 3
  • 注解的本质 「java.lang.annotation.Annotation」接口中有这么一句话,用来描述『注解』...
    程序员阿浪阅读 591评论 0 0
  • 什么是注解? Annontation(注解)是Java5开始引入的新特征。它提供了一种安全的类似注释的机制,用来将...
    星星_点灯阅读 1,135评论 0 1
  • 表情是什么,我认为表情就是表现出来的情绪。表情可以传达很多信息。高兴了当然就笑了,难过就哭了。两者是相互影响密不可...
    Persistenc_6aea阅读 124,019评论 2 7