前言
在 Java 中, 注解 (Annotation) 非常重要, 但对于很多开发者来说, 这并不容易理解, 甚至觉得有点神秘
今天, 我将献上一份 Java 注解的介绍 & 实战攻略, 希望你们会喜欢.
目录
示意图
1. 简介
定义: 一种标识 / 标签
注解属于 Java 中的一种类型, 同 类 class 和 接口 interface 一样
在 Java SE 5.0 开始引入
基础作用: 标识 / 解释 Java 代码
2. 应用场景
面向 编译器 / APT 使用
APT(Annotation Processing Tool)
: 提取 & 处理 Annotation 的代码
因为当开发者使用 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 world")
- 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 注解的注解 作用的类的子类可以继承该类的注解
前提: 子类没有被任何注解应用
如, 注解 @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 https://github.com/Carson-Ho/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 https://github.com/Carson-Ho/Annation_Debug
6. 总结
本文全面讲解了 Java 注解 (Annotation) 的相关知识, 相信您对 Java 注解已经了解深刻.
下面我将继续对 Android 中的知识进行深入讲解 , 有兴趣可以继续关注 Carson_Ho 的安卓开发笔记
请点赞! 因为你的鼓励是我写作的最大动力!
来源: http://www.jianshu.com/p/9f29fb37c840