- 在
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%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 {
}
- 1
- 2
- 3
- 4
- 5
- 6
- 注解作用于
Java
代码 & 解释Java
代码
// 注解作用于Java代码 & 解释Java代码
@Carson_Annotation
public class Carson {
}
- 1
- 2
- 3
- 4
- 5
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 {www.yongshiyule178.com}
@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" www.huachengj1980.com/) @Person(role="PM") public class Carson{
注:一个注解可以被多个元注解作用
@Inherited
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface Carson_Annotation {
3.1.4 元注解类型总结
![示意图](//upload-images.jianshu.io/upload_images/944365-6df4776e4dfcc865.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
3.2 Java内置的注解
- 定义:即Java内部已经实现好的注解
-
类型:
Java
中 内置的注解有5类,具体包括: -
下面我将对这5类内置注解进行讲解
@Deprecated
- 定义:过时注解
- 作用:标记已过时 & 被抛弃的元素(类、方法等)
- 具体使用
// 用 注解@Deprecated 标记类中已过时的 方法Hello()
public class Buyer2 {
@Deprecated
public void Hello(www.078881.cn ){ 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_www.mcyllpt.com 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(www.leyou2.net)
public boolean isAnnotationPresent(Class<? extends Annotation> annotationClass) {} <-- 步骤2:获取 注解对象(Annotation)--> // 手段1:采用getAnnotation(www.meiwanyule.cn ) ;返回指定类型的注解 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 {
}
- 1
- 2
- 3
- 4
- 5
- 6
- 步骤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)); } }
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 步骤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()); } } } }
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
5.3 测试结果
5.4 Demo地址
Carson_Ho的Github地址:Annation_Debug
6. 总结
-
本文全面讲解了
Java
注解(Annotation
)的相关知识,相信您对Java
注解已经了解深刻。 -
下面我将继续对
Android
中的知识进行深入讲解 ,有兴趣可以继续关注Carson_Ho的安卓开发笔记