Carson带你学Java:带你一步步探索神秘的注解(Annotation)


前言

  • Java中,注解(Annotation)非常重要,但对于很多开发者来说,这并不容易理解,甚至觉得有点神秘
  • 今天,我将献上一份 Java注解的介绍 & 实战攻略,希望你们会喜欢。

目录

示意图

1. 简介

  • 定义:一种标识 / 标签
  1. 注解属于 Java中的一种类型,同 类class 和 接口interface 一样
  2. Java SE 5.0开始引入
  • 基础作用:标识 / 解释 Java 代码

2. 应用场景

  • 面向 编译器 / APT 使用
  1. APT(Annotation Processing Tool):提取 & 处理 Annotation 的代码
  2. 因为当开发者使用Annotation 修饰类、方法、方法 等成员后,这些 Annotation 不会自己生效,必须由开发者提供相应的代码来提取并处理 Annotation 信息。这些处理提取和处理 Annotation 的代码统称为 APT
  • 所以说,注解除了最基本的解释 & 说明代码,注解的应用场景还取决于你想如何利用它
  • 下面我举几个典型的应用场景:

场景1:测试代码

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

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

场景2:简化使用 & 降低代码量

Http网络请求库 Retrofit & IOC 框架ButterKnife

<-- Http网络请求库 Retrofit -->

// 采用 注解 描述 网络请求参数
public interface GetRequest_Interface {

 @GET("ajax.php?a=fy&f=auto&t=auto&w=hello%20world")
    Call<Translation> getCall();

 }

<-- IOC 框架ButterKnife -->
public class MainActivity extends AppCompatActivity {

// 通过注解设置资源
@BindView(R.id.test)
    TextView mTv;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        ButterKnife.bind(this);
    }
}

3. 注解的类型

注解的类型包括:元注解 、 Java内置注解 & 自定义注解。

3.1 元注解

3.1.1 简介
  • 定义:一种标识 / 标签

是一种基本注解 = Android系统内置的注解

  • 作用:标识 / 解释 开发者自定义的注解
3.1.2 注解、元注解 & Java代码的关系
  • 元注解作用于注解 & 解释注解
// 元注解 作用于注解 & 解释注解
// 元注解在注解定义时进行定义
@元注解
public @interface Carson_Annotation {

}
  • 注解作用于Java代码 & 解释Java代码
// 注解作用于Java代码 & 解释Java代码
@Carson_Annotation
public class Carson {
}

3.1.3 元注解类型介绍
  • 元注解共有5种,包括:
示意图
  • 下面我将一一介绍

@Retention

  • 定义:保留注解
  • 作用:解释 / 说明了注解的生命周期
  • 具体使用
// 元注解@Retention(RetentionPolicy.RUNTIME)的作用:说明 注解Carson_Annotation的生命周期 = 保留到程序运行时 & 被加载到 JVM 中
@Retention(RetentionPolicy.RUNTIME)
public @interface Carson_Annotation {
}

<-- 元注解@Retention参数说明 -->
// RetentionPolicy.RUNTIME:注解保留到程序运行时 & 会被加载进入到 JVM 中,所以在程序运行时可以获取到它们
// RetentionPolicy.CLASS:注解只被保留到编译进行时 & 不会被加载到 JVM 
// RetentionPolicy.SOURCE:注解只在源码阶段保留 & 在编译器进行编译时将被丢弃忽视。

@Documented

  • 定义:Java文档注解
  • 作用:将注解中的元素包含到 Javadoc文档中
  • 具体使用
// 元注解@Documented作用:说明 注解Carson_Annotation的元素包含到 Javadoc 文档中
@Documented
public @interface Carson_Annotation {
}

@Target

  • 定义:目标注解
  • 作用:限定了注解作用的目标范围,包括类、方法等等
  • 具体使用
// 元注解@Target作用:限定了注解Carson_Annotation作用的目标范围 = 方法
// 即注解Carson_Annotation只能用于解释说明 某个方法
@Target(ElementType.METHOD)
public @interface Carson_Annotation {
}

<-- @Target取值参数说明 -->
// ElementType.PACKAGE:可以给一个包进行注解
// ElementType.ANNOTATION_TYPE:可以给一个注解进行注解
// ElementType.TYPE:可以给一个类型进行注解,如类、接口、枚举
// ElementType.CONSTRUCTOR:可以给构造方法进行注解
// ElementType.METHOD:可以给方法进行注解
// ElementType.PARAMETER 可以给一个方法内的参数进行注解
// ElementType.FIELD:可以给属性进行注解
// ElementType.LOCAL_VARIABLE:可以给局部变量进行注解


@Inherited

  • 定义:继承注解
  • 作用:使得一个 被@Inherited注解的注解 作用的类的子类可以继承该类的注解
  1. 前提:子类没有被任何注解应用
  2. 如,注解@Carson_Annotation(被元注解@Inherited 注解)作用于A类,那么A类的子类则继承了A类的注解
  • 具体使用
// 元注解@Inherited 作用于 注解Carson_Annotation
@Inherited
public @interface Carson_Annotation {
}


// 注解Carson_Annotation 作用于A类
@Carson_Annotation
public class A {
  }

// B类继承了A类,即B类 = A类的子类,且B类没被任何注解应用
// 那么B类继承了A类的注解 Carson_Annotation
public class B extends A {}

@Repeatable

  • 定义:可重复注解

Java 1.8后引进

  • 作用:使得作用的注解可以取多个值
  • 具体使用
// 1. 定义 容器注解 @ 职业
public @interface Job {
    Person[]  value();
}
<-- 容器注解介绍 -->
// 定义:本身也是一个注解
// 作用:存放其它注解
// 具体使用:必须有一个 value 属性;类型 = 被 @Repeatable 注解的注解数组
// 如本例中,被 @Repeatable 作用 = @Person ,所以value属性 = Person []数组

// 2. 定义@Person 
// 3. 使用@Repeatable 注解 @Person
// 注:@Repeatable 括号中的类 = 容器注解
@Repeatable(Job.class)
public @interface Person{
    String role default "";
}

// 在使用@Person(被@Repeatable 注解 )时,可以取多个值来解释Java代码
// 下面注解表示:Carson类即是产品经理,又是程序猿
@Person(role="coder")
@Person(role="PM")
public class Carson{

}


注:一个注解可以被多个元注解作用

@Inherited
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface Carson_Annotation {
}
3.1.4 元注解类型总结
示意图

3.2 Java内置的注解

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

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

    示意图

  • 下面我将对这5类内置注解进行讲解

@Deprecated

  • 定义:过时注解
  • 作用:标记已过时 & 被抛弃的元素(类、方法等)
  • 具体使用
// 用 注解@Deprecated 标记类中已过时的 方法Hello()
public class Buyer2   {

    @Deprecated
    public void Hello(){
        System.out.println("Hello 2015!");
    }
}

使用该类中被 @Deprecated 作用的方法时,IDE会提示该方法已过时 / 抛弃

示意图

@Override

  • 定义:复写注解
  • 作用:标记该方法需要被子类复写
  • 具体使用:该方法大家很熟悉了,此处不作过多描述

@SuppressWarnings

  • 定义:阻止警告注解
  • 作用:标记的元素会阻止编译器发出警告提醒

主要应用于开发者需要忽略警告时

  • 具体使用
// 在括号内传入需要忽略警告的属性
@SuppressWarnings("deprecation")
public void test(){
    Buyer2 mBuyer2 = new Buyer2();
    mBuyer2.hello();
    // IDE将不会发出警告(即不会在hello()出现划线)
}

@SafeVarargs

  • 定义:参数安全类型注解

Java 1.7 后引入

  • 作用:提醒开发者不要用参数做不安全的操作 & 阻止编译器产生 unchecked警告
  • 具体使用
// 以下是官方例子
// 虽然编译阶段不报错,但运行时会抛出 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

  • 定义:函数式接口注解

Java 1.8 后引入的新特性

  • 作用:表示该接口 = 函数式接口

函数式接口 (Functional Interface) = 1个具有1个方法的普通接口

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

<--额外:为什么要用函数式接口标记 -->
// 原因:函数式接口很容易转换为 Lambda 表达式
// 这是另外一个很大话题,此处不作过多讲解,感兴趣的同学可自行了解

Java内置注解类型总结

示意图

3.3 自定义注解

  • 定义:开发者自定义注解
  • 具体使用:下面第4节将这种介绍。

4. 使用介绍

注解的基础使用包括定义、属性 & 具体使用

4.1 注解的定义

// 通过 @interface 关键字进行定义
// 形式类似于接口,区别在于多了一个 @ 符号

public @interface Carson_Annotation {

}

// 上面的代码创建了一个名为 Carson_Annotaion 的注解

4.2 注解的属性

<-- 1. 定义 注解的属性 -->
// 注解的属性 在定义该注解本身时 进行定义
public @interface Carson_Annotation {
// 注解的属性 = 成员变量
// 注解只有成员变量,没有方法

    // 注解@Carson_Annotation中有2个属性:id 和 msg  
    int id();
    String msg() default "Hi" ;

    // 说明:
      // 注解的属性以 “无形参的方法” 形式来声明
      // 方法名 = 属性名
      // 方法返回值 = 属性类型 = 8 种基本数据类型 + 类、接口、注解及对应数组类型
      // 用 default 关键值指定 属性的默认值,如上面的msg的默认值 = ”Hi“
}

<-- 2. 赋值 注解的属性 -->
// 注解的属性在使用时进行赋值
// 注解属性的赋值方式 = 注解括号内以 “value=”xx” “ 形式;用 ”,“隔开多个属性

// 注解Carson_Annotation 作用于A类
// 在作用 / 使用时对注解属性进行赋值
@Carson_Annotation(id=1,msg="hello,i am Carson")
public class A {
  }

// 特别说明:若注解只有一个属性,则赋值时”value“可以省略

// 假设注解@Carson_Annotation只有1个msg属性
// 赋值时直接赋值即可
@Carson_Annotation("hello,i am Carson")
public class A {
  }

4.3 注解的应用

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

    @Carson_Annotation
    int a;
    
    @Carson_Annotation
    public void bMethod(){}

}

// 即注解@Carson_Annotation 用于标识解释 Carson类 / a变量 / b方法

4.4 获取注解

  • 定义:即获取某个对象上的所有注解。
  • 实现手段:Java 反射技术

由于反射需要较高时间成本,所以注解使用时要谨慎考虑时间成本

  • 具体使用
<-- 步骤1:判断该类是否应用了某个注解 -->
// 手段:采用 Class.isAnnotationPresent() 
public boolean isAnnotationPresent(Class<? extends Annotation> annotationClass) {}

<-- 步骤2:获取 注解对象(Annotation)-->
  // 手段1:采用getAnnotation()  ;返回指定类型的注解
public <A extends Annotation> A getAnnotation(Class<A> annotationClass) {}
  // 手段2:采用getAnnotations() ;返回该元素上的所有注解
public Annotation[] getAnnotations() {}

  • 使用实例
    下面我将用一个例子展示如何获取一个类、方法 & 成员变量上的注解类型

步骤1:自定义2个注解

Carson_Annotation.java

// 因为注解@Carson_Annotation需要在程序运行时使用
// 所以必须采用元注解Retention(RetentionPolicy.RUNTIME)
@Retention(RetentionPolicy.RUNTIME)
public @interface Carson_Annotation {

    // 注解@Carson_Annotation中有2个属性:id 和 msg
    int id();
    String msg() default "Hi";
}

Carson_Annotation2.java

// 因为注解@Carson_Annotation2需要在程序运行时使用
// 所以必须采用元注解Retention(RetentionPolicy.RUNTIME)
@Retention(RetentionPolicy.RUNTIME)
public @interface Carson_Annotation2 {

    // 注解@Carson_Annotation2中有2个属性:id 和 msg
    int id();
    String msg() default "Hi";
}

步骤2:定义1个被注解的类

Test.java

// 1个注解作用于Test类
@Carson_Annotation(id = 1,msg="我是类Test")
public class Test {

    // 1个注解 作用于Test类成员变量a
    @Carson_Annotation(id = 2,msg="我是变量a")
    int a;
  
    // 2个注解 作用于Test类方法
    @Carson_Annotation(id = 3,msg="我是方法b")
    @Carson_Annotation2(id = 4,msg="我是方法bb(来自第2个注解)")
    public void bMethod(){}

    }

步骤3:分别获取一个类、方法 & 成员变量上的注解

    private static final String TAG = "Annotation";

        /**
         * 讲解1:获取类上的注解
         */

        // 1. 判断Test类是否应用了注解@Carson_Annotation
        boolean hasAnnotation = Test.class.isAnnotationPresent(Carson_Annotation.class);

        // 2. 如果应用了注解 = hasAnnotation = true
        //    则获取类上的注解对象
        if ( hasAnnotation ) {
            Carson_Annotation classAnnotation = Test.class.getAnnotation(Carson_Annotation.class);

            // 3. 获取注解对象的值
            Log.d(TAG, "我是Test类上的注解");
            Log.d(TAG, "id:" + classAnnotation.id());
            Log.d(TAG, "msg:" + classAnnotation.msg());
        }

        /**
         * 讲解2:获取类成员变量a上的注解
         */
        try {
            // 1. 获取类上的成员变量a
            Field a = Test.class.getDeclaredField("a");
            a.setAccessible(true);
            
            // 2. 获取成员变量a上的注解@Carson_Annotation
            Carson_Annotation variableAnnotation = a.getAnnotation(Carson_Annotation.class);

            // 3. 若成员变量应用了注解 = hasAnnotation = true
            //    则获取注解对象上的值 = id & msg
            if ( variableAnnotation != null ) {
                Log.d(TAG, "我是类成员变量a上的注解");
                Log.d(TAG, "id:" + variableAnnotation.id());
                Log.d(TAG, "msg:" + variableAnnotation.msg());

            }

            /**
             * 讲解3:获取类方法bMethod上的注解
             */

            // 1. 获取类方法bMethod
            Method testMethod = Test.class.getDeclaredMethod("bMethod");


            // 2. 获取方法上的注解
            if ( testMethod != null ) {
                // 因为方法上有2个注解,所以采用getAnnotations()获得所有类型的注解
                Annotation[] ans = testMethod.getAnnotations();

                Log.d(TAG, "我是类方法bMethod的注解");

                // 3. 获取注解的值(通过循环)
                for( int i = 0;i < ans.length  ;i++) {


                    Log.d(TAG, "类方法bMethod的" + "注解"+ i+ ans[i].annotationType().getSimpleName());
                }
            }
        } catch (NoSuchFieldException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
            System.out.println(e.getMessage());
        } catch (SecurityException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
            System.out.println(e.getMessage());
        } catch (NoSuchMethodException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
            System.out.println(e.getMessage());
        }
    }
  • 测试结果
示意图
  • Demo地址

Carson的Github地址:Java_Annotation


5. 实例演练

下面,我将通过注解实现一个最常见的应用场景:测试代码

5.1 实例说明

通过注解,检查一个类中的方法是否存在异常

5.2 具体步骤

  • 步骤1:自定义测试注解

Carson_Test.java

// 因为注解@Carson_Test需要在程序运行时使用
// 所以必须采用元注解Retention(RetentionPolicy.RUNTIME)
@Retention(RetentionPolicy.RUNTIME)
public @interface Carson_Test {

}
  • 步骤2:定义需要测试的类

Test.java

public class Test {

    @Carson_Test
    public void method1(){
        System.out.println("method1正常运行 = " + (1+1));
    }
    @Carson_Test
    public void method2(){
        System.out.println("method2正常运行 = " + (2*3));
    }

    @Carson_Test
    public void method3(){
        System.out.println("method3正常运行 = " + (2/0));
    }
    
}
  • 步骤3:采用注解 测试 类的方法 是否存在 Bug

MainActivity.java

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        // 1. 获取测试类Test对象
        Test mTest = new Test();
        Class mTest_Class = mTest.getClass();

        // 2. 获取测试类Test的所有方法(通过数组存放)
        Method[] method = mTest_Class.getDeclaredMethods();
        
        // 3. 通过注解@Carson_Test 测试类中的方法
        
        // a. 遍历类中所有方法
        for ( Method m: method ) {
            // b. 只有被 @Carson_Test 标注过的方法才允许进行测试
            if ( m.isAnnotationPresent( Carson_Test.class )) {
                try {
                    m.setAccessible(true);
                    // c. 通过反射调用测试类中的方法
                    m.invoke(mTest);
                    // d. 捕捉方法出现的异常 & 输出异常信息
                } catch (Exception e) {
                    System.out.println( "Test类出现Bug了!!!");
                    System.out.println( "出现Bug的方法:" + m.getName());
                    System.out.println( "Bug类型:" + e.getCause().getClass().getSimpleName());
                    System.out.println( "Bug信息:" + e.getCause().getMessage());
                }
            }
        }
}

5.3 测试结果

image.png

5.4 Demo地址

Carson_Ho的Github地址:Annation_Debug


6. 总结

本文全面讲解了Java注解(Annotation)的相关知识,相信您对Java注解已经了解深刻。


欢迎关注Carson_Ho的简书!

分享Android技术干货,追求短、平、快,但却不缺深度


请点赞!因为你的鼓励是我写作的最大动力!

相关文章阅读
Android开发:最全面、最易懂的Android屏幕适配解决方案
Android事件分发机制详解:史上最全面、最易懂
Android开发:史上最全的Android消息推送解决方案
Android开发:最全面、最易懂的Webview详解
Android开发:JSON简介及最全面解析方法!
Android四大组件:Service服务史上最全面解析
Android四大组件:BroadcastReceiver史上最全面解析

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

推荐阅读更多精彩内容