您的位置:首页 > 编程语言 > Java开发

Java篇 - 深入了解注解

2019-01-22 15:17 288 查看

注解在我们平时的开发中到处可见,无论是Javaee还是android开发,如JUnit,ButterKnife,Dagger2,Retrofit,EventBus,android.support.annotation。

今天我来带大家整理一下注解,做到知其然更知其所以然。

 

目录:

  1. 注解的概述
  2. Java预置的注解
  3. 元注解
  4. 注解的属性
  5. 注解与反射(android permission为例)
  6. @Documented如何使用
  7. 注解的性能问题

 

 

1. 注解的概述

 

  • 1.1 注解的概念

官方版:

Java注解用于为Java代码提供元数据。作为元数据,注解不直接影响你的代码执行,但也有一些类型的注解实际上可以用于这一目的。Java注解是从Java5开始添加到Java的。

白话版: 

注解就是在代码中某些部分贴上去的一些贴纸。

 

  • 1.2 注解的分类
  1. Java自带的标准注解,包括@Override(标明重写某个方法)、@Deprecated(标明某个类或方法过时)和@SuppressWarnings(标明要忽略的警告),使用这些注解后编译器就会进行检查。
  2. 元注解,元注解是用于定义注解的注解,包括@Retention(标明注解被保留的阶段)、@Target(标明注解使用的范围)、@Inherited(标明注解可继承)、@Documented(标明是否生成javadoc文档)。
  3. 自定义注解,可以根据自己的需求定义注解。

 

  • 1.3 注解的作用

注解是与源码紧密绑定的,注解的主要用途有以下几个:

  1. 生成文档,通过代码里标识的元数据生成javadoc文档。
  2. 编译检查,通过代码里标识的元数据让编译器在编译期间进行检查。
  3. 编译时动态处理,编译时通过代码里标识的元数据动态处理,比如动态生成代码。
  4. 运行时动态处理,运行时通过代码里标识的元数据动态处理,比如使用反射注入实例。

 

 

2. Java预置的注解

Java预置了一些注解,接下来一一介绍。

 

  • 2.1 @Deprecated
  1. @Documented
  2. @Retention(RetentionPolicy.RUNTIME)
  3. @Target(value={CONSTRUCTOR, FIELD, LOCAL_VARIABLE, METHOD, PACKAGE, PARAMETER, TYPE})
  4. public @interface Deprecated {
  5. }
[/code]

这个注解是用来标记过时的元素,编译器在编译阶段遇到这个注解时会发出提醒警告,告诉开发者正在调用一个过时的元素比如过时的方法、过时的类、过时的成员变量。

比如:

  1. private static final class Test {
  2. @Deprecated
  3. void sayHello() {
  4. System.out.println("say hello");
  5. }
  6. }
  7. public static void main(String[] args) {
  8. Test test = new Test();
  9. test.sayHello();
  10. }
[/code]

这时sayHello()方法上面被一条直线划了一条,这其实就是编译器识别后的提醒效果:

 

  • 2.2 @Override
  1. @Target(ElementType.METHOD)
  2. @Retention(RetentionPolicy.SOURCE)
  3. public @interface Override {
  4. }
[/code]

这个注解再熟悉不过了,提示该方法是接口方法的实现或者是子类重写的父类的方法。

 

  • 2.3 @SuppressWarnings
  1. @Target({TYPE, FIELD, METHOD, PARAMETER, CONSTRUCTOR, LOCAL_VARIABLE})
  2. @Retention(RetentionPolicy.SOURCE)
  3. public @interface SuppressWarnings {
  4. String[] value();
  5. }
[/code]

阻止警告的意思,上面说过调用被@Deprecated注解的方法后,编译器会警告提醒,而有时候开发者会忽略这种警告,他们可以在调用的地方通过@SuppressWarnings达到目的。

如:

  1. @SuppressWarnings("deprecation")
  2. public static void main(String[] args) {
  3. Test test = new Test();
  4. test.sayHello();
  5. }
[/code]

这个时候sayHello()就不会被编译器处以下划线的警告了。

 

  • 2.4 @SafeVarargs
  1. @Documented
  2. @Retention(RetentionPolicy.RUNTIME)
  3. @Target({ElementType.CONSTRUCTOR, ElementType.METHOD})
  4. public @interface SafeVarargs {}
[/code]

参数安全类型注解。它的目的是提醒开发者不要用参数做一些不安全的操作,它的存在会阻止编译器产生unchecked这样的警告,它是在Java 1.7的版本中加入的。

如:

  1. @SafeVarargs
  2. static void collects(List<String>... stringLists) {
  3. Object[] array = stringLists;
  4. List<Integer> tmpList = Arrays.asList(42);
  5. // Semantically invalid, but compiles without warnings
  6. array[0] = tmpList;
  7. // Oh no, ClassCastException at runtime!
  8. String s = stringLists[0].get(0);
  9. }
[/code]

上面的代码中,编译阶段不会报错,但是运行时会抛出ClassCastException这个异常,所以它虽然告诉开发者要妥善处理,但是开发者自己还是搞砸了。

Java官方文档说,未来的版本会授权编译器对这种不安全的操作产生错误警告。 

 

 

3. 元注解

元注解是可以注解到注解上的注解,或者说元注解是一种基本注解,但是它能够应用到其它的注解上面。 

元标签有@Retention、@Documented、@Target、@Inherited、@Repeatable 5种。

 

  • 3.1 @Retention

Retention的英文意为保留期的意思。当@Retention应用到一个注解上的时候,它解释说明了这个注解的的存活时间。

它的取值如下: 

  • RetentionPolicy.SOURCE 注解只在源码阶段保留,在编译器进行编译时它将被丢弃忽视。 
  • RetentionPolicy.CLASS 注解只被保留到编译进行的时候,它并不会被加载到JVM中。 
  • RetentionPolicy.RUNTIME 注解可以保留到程序运行的时候,它会被加载进入到JVM中,所以在程序运行时可以获取到它们。

如@Deprecated是保留到运行时,运行时可以获取到它,而@Override保留到源码期。

 

  • 3.2 @Documented

顾名思义,这个元注解肯定是和文档有关,它的作用是能够将注解中的元素包含到Javadoc中去。

 

  • 3.3 @Target

Target是目标的意思,@Target指定了注解运用的地方。

你可以这样理解,当一个注解被@Target注解时,这个注解就被限定了运用的场景。

@Target有下面的取值:

  1. ElementType.ANNOTATION_TYPE 可以给一个注解进行注解。
  2. ElementType.CONSTRUCTOR 可以给构造方法进行注解。
  3. ElementType.FIELD 可以给属性进行注解。
  4. ElementType.LOCAL_VARIABLE 可以给局部变量进行注解。
  5. ElementType.METHOD 可以给方法进行注解。
  6. ElementType.PACKAGE 可以给一个包进行注解。
  7. ElementType.PARAMETER 可以给一个方法内的参数进行注解。
  8. ElementType.TYPE 可以给一个类型进行注解,比如类、接口、枚举。

 

  • 3.4 @Inherited

Inherited是继承的意思,但是它并不是说注解本身可以继承,而是说如果一个超类被@Inherited注解过的注解进行注解的话,那么如果它的子类没有被任何注解应用的话,那么这个子类就继承了超类的注解。 
 

  1. @Inherited
  2. @Retention(RetentionPolicy.RUNTIME)
  3. @interface TestAnno {
  4. }
  5. @TestAnno
  6. protected class Human {
  7. }
  8. private final class Man extends Human {
  9. }
[/code]

注解TestAnno被@Inherited修饰,Human类被TestAnno注解,类Man继承Human,那么类Human也拥有TestAnno这个注解。 

 

  • 3.5 @Repeatable​​​​​​​

Repeatable自然是可重复的意思,@Repeatable是Java 1.8才加进来的,所以算是一个新的特性。 

什么样的注解会多次应用呢?通常是注解的值可以同时取多个。 

比如一个人可以是程序员也可以是产品经理:

  1. @interface Works {
  2. Work[] value();
  3. }
  4. @Repeatable(Works.class)
  5. @interface Work {
  6. String role = "";
  7. }
  8. @Work(role = "coder")
  9. @Work(role = "pm")
  10. private final class Person {
  11. }
[/code]

@Repeatable注解了Work,而@Repeatable后面括号中的类相当于一个容器注解。 

什么是容器注解呢?就是用来存放其它注解的容器,它本身也是一个注解。

  1. @interface Works {
  2. Work[] value();
  3. }
[/code]

按照规定,它里面必须要有一个value的属性,属性类型是一个被@Repeatable注解过的注解数组。 

 

 

4. 注解的属性

注解的属性也叫做成员变量。注解只有成员变量,没有方法。注解的成员变量在注解的定义中以"无形参的方法"形式来声明,其方法名定义了该成员变量的名字,其返回值定义了该成员变量的类型。

  1. @Target(ElementType.TYPE)
  2. @Retention(RetentionPolicy.RUNTIME)
  3. public @interface TestAnno {
  4. int id();
  5. String msg();
  6. }
[/code]

上面代码定义了TestAnnotation这个注解中拥有id和msg两个属性,在使用的时候,我们应该给它们进行赋值。

赋值的方式是在注解的括号内以 value=""形式,多个属性之前用","隔开。 

  1. @Target(ElementType.TYPE)
  2. @Retention(RetentionPolicy.RUNTIME)
  3. public @interface TestAnno {
  4. int id();
  5. String msg();
  6. }
  7. @TestAnno(id=3, msg="msg...")
  8. public class Test {
  9. }
[/code]

需要注意的是,在注解中定义属性时它的类型必须是8种基本数据类型外加类、接口、注解及它们的数组。 

注解中属性可以有默认值,默认值需要用 default 关键值指定。比如:

  1. @Target(ElementType.TYPE)
  2. @Retention(RetentionPolicy.RUNTIME)
  3. public @interface TestAnno {
  4. int id() default -1;
  5. String msg() default "hi";
  6. }
[/code]

那么使用时可以直接这样:

  1. @TestAnno()
  2. public class Test1 {
  3. }
[/code]

另外,还有一种情况:如果一个注解内仅有一个名字为value的属性时,应用这个注解时可以直接把属性值填写到括号内。

如:

  1. public @interface Check {
  2. String value();
  3. }
  4. public void testCheck() {
  5. @Check("i")
  6. int i;
  7. }
[/code]

最后,还需要注意的一种情况是一个注解没有任何属性,比如:

  1. public @interface Perform {
  2. }
[/code]

使用时,直接:

  1. public @interface Perform {
  2. }
  3. @Perform
  4. public void testPerform() {
  5. }
[/code]

 

 
[/code]

5. 注解与反射(android permission为例)​​​​​​​

前面我们说了,注解相当于在代码里贴贴纸,那么贴完贴纸之后有什么用呢?

这就需要反射来实现了,注解与反射是一对天生的搭档。

对于反射不熟悉的同学,可以看看我的这篇文章:《Java篇 - 反射机制分析(附面试中的坑)》https://www.geek-share.com/detail/2758398564.html

 

  • 5.1 Class对象提供的api
public boolean isAnnotationPresent(Class<? extends Annotation> annotationClass) {}
[/code]

isAnnotationPresent()方法判断Class是否应用了某个注解。

public <A extends Annotation> A getAnnotation(Class<A> annotationClass) {}
[/code]

getAnnotation()方法获取某个Annotation对象。

public Annotation[] getAnnotations() {}
[/code]

getAnnotations()方法获取所有的Annotation对象。

 

  • 5.2 例子

我在平时做Android的时候,Android 6.0及其以上需要动态获取权限,也是Android设计的一个安全机制。

先自定义三个注解:

  1. @Retention(RetentionPolicy.RUNTIME)
  2. @Target(ElementType.METHOD)
  3. public @interface OnMPermissionDenied {
  4. int value();
  5. }
  6. @Retention(RetentionPolicy.RUNTIME)
  7. @Target(ElementType.METHOD)
  8. public @interface OnMPermissionGranted {
  9. int value();
  10. }
  11. @Retention(RetentionPolicy.RUNTIME)
  12. @Target(ElementType.METHOD)
  13. public @interface OnMPermissionNeverAskAgain {
  14. int value();
  15. }
[/code]

分别是权限被拒绝,权限被运行和权限不再询问,Target都是修饰方法,保留期都是运行时,可以在运行时获取到这些注解。


再来先来看看如何调用权限请求。

先在Activity中发起权限请求:

MPermissionUtils.with(activity).addRequestCode(REQ_CODE_CAMERA_PERMISSION).permissions(CAMERA).request();
[/code]

最终调用到这个函数: 

  1. @TargetApi(value = Build.VERSION_CODES.M)
  2. private static void requestPermissions(Object object, int requestCode, String[] permissions) {
  3. if (!APIUtils.hasM()) {
  4. doPermissionExecuteSuccess(object, requestCode);
  5. return;
  6. }
  7. List<String> deniedPermissions = findDeniedPermissions(getActivity(object), permissions);
  8. if (deniedPermissions.size() > 0) {
  9. if (object instanceof Activity) {
  10. ((Activity) object).requestPermissions(
  11. deniedPermissions.toArray(new String[deniedPermissions.size()]), requestCode);
  12. } else if (object instanceof Fragment) {
  13. ((Fragment) object).requestPermissions(
  14. deniedPermissions.toArray(new String[deniedPermissions.size()]), requestCode);
  15. } else {
  16. throw new IllegalArgumentException(
  17. object.getClass().getName() + " is not supported");
  18. }
  19. } else {
  20. doPermissionExecuteSuccess(object, requestCode);
  21. }
  22. }
[/code]

需要三个参数:object为Activity或者fragment,requestCode为请求码,作为返回结果的唯一标识,permissions为权限数组。

 

再来看看如何处理权限回调,在刚刚请求权限的Activity中,有这个注解方法:

  1. @OnMPermissionGranted(PermissionService.REQ_CODE_CAMERA_PERMISSION)
  2. public void onCameraPermissionSuccess() {
  3. PermissionService.requestCameraPermissionSuccess(new PermissionCallback() {
  4. @Override
  5. public void onSuccessful() {
  6. CaptureActivity.openCaptureActivityForResult(TransactionSendActivity.this, QRCODE_ACTIVITY_CODE);
  7. }
  8. });
  9. }
[/code]

那么什么时候会回调这个方法呢,这就涉及到反射了。

申请权限后,Activity会回调onRequestPermissionsResult()方法:

  1. @Override
  2. public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
  3. onRequestPermissionsResult(this, requestCode, permissions, grantResults);
  4. }
[/code]

然后在该方法里面通过反射获取到注解:

  1. public static void onRequestPermissionsResult(Activity activity, int requestCode,
  2. String[] permissions, int[] grantResults) {
  3. requestResult(activity, requestCode, permissions, grantResults);
  4. }
  5. private static void requestResult(Object obj, int requestCode, String[] permissions,
  6. int[] grantResults) {
  7. List<String> deniedPermissions = new ArrayList<>();
  8. for (int i = 0; i < grantResults.length; i++) {
  9. if (grantResults[i] != PackageManager.PERMISSION_GRANTED) {
  10. deniedPermissions.add(permissions[i]);
  11. }
  12. }
  13. if (deniedPermissions.size() > 0) {
  14. if (hasNeverAskAgainPermission(getActivity(obj), deniedPermissions)) {
  15. doPermissionExecuteFailAsNeverAskAgain(obj, requestCode);
  16. } else {
  17. doPermissionExecuteFail(obj, requestCode);
  18. }
  19. } else {
  20. doPermissionExecuteSuccess(obj, requestCode);
  21. }
  22. }
  23. @TargetApi(value = Build.VERSION_CODES.M)
  24. private static boolean hasNeverAskAgainPermission(Activity activity, List<String> permission) {
  25. for (String value : permission) {
  26. if (activity.checkSelfPermission(value) != PackageManager.PERMISSION_GRANTED &&
  27. !activity.shouldShowRequestPermissionRationale(value)) {
  28. return true;
  29. }
  30. }
  31. return false;
  32. }
  33. private static void doPermissionExecuteSuccess(Object activity, int requestCode) {
  34. executeMethod(activity, findPermissionMethodWithRequestCode(getEnsureClass(activity),
  35. OnMPermissionGranted.class, requestCode));
  36. }
  37. private static void doPermissionExecuteFail(Object activity, int requestCode) {
  38. executeMethod(activity, findPermissionMethodWithRequestCode(getEnsureClass(activity),
  39. OnMPermissionDenied.class, requestCode));
  40. }
  41. private static void doPermissionExecuteFailAsNeverAskAgain(Object activity, int requestCode) {
  42. executeMethod(activity, findPermissionMethodWithRequestCode(getEnsureClass(activity),
  43. OnMPermissionNeverAskAgain.class, requestCode));
  44. }
  45. private static void executeMethodWithParam(Object activity, Method executeMethod,
  46. Object... args) {
  47. if (executeMethod != null) {
  48. try {
  49. if (!executeMethod.isAccessible()) {
  50. executeMethod.setAccessible(true);
  51. }
  52. executeMethod.invoke(activity, args);
  53. } catch (Exception e) {
  54. // do nothing
  55. }
  56. }
  57. }
  58. private static <A extends Annotation> Method findPermissionMethodWithRequestCode(Class clazz,
  59. Class<A> annotation, int requestCode) {
  60. for (Method method : clazz.getDeclaredMethods()) {
  61. if (method.isAnnotationPresent(annotation)) {
  62. if (isEqualPermissionRequestCodeFromAnnotation(method, annotation, requestCode)) {
  63. return method;
  64. }
  65. }
  66. }
  67. return null;
  68. }
  69. private static boolean isEqualPermissionRequestCodeFromAnnotation(Method m, Class clazz,
  70. int requestCode) {
  71. if (clazz.equals(OnMPermissionDenied.class)) {
  72. return requestCode == m.getAnnotation(OnMPermissionDenied.class).value();
  73. } else if (clazz.equals(OnMPermissionGranted.class)) {
  74. return requestCode == m.getAnnotation(OnMPermissionGranted.class).value();
  75. } else if (clazz.equals(OnMPermissionNeverAskAgain.class)) {
  76. return requestCode == m.getAnnotation(OnMPermissionNeverAskAgain.class).value();
  77. } else {
  78. return false;
  79. }
  80. }
[/code]

可以看到,findPermissionMethodWithRequestCode()和isEqualPermissionRequestCodeFromAnnotation()这两个方法就是通过反射获取被注解的方法,然后执行被注解的方法,就是上面的onCameraPermissionSuccess()。

 

 

6. @Documented如何使用​​​​​​​

先定义一个注解:

  1. @Documented
  2. @interface DocumentedtAnnotation{
  3. String key() default "csdn" ;
  4. String value() default "kuang" ;
  5. }
[/code]

再定义一个需要生成javadoc的类:

  1. public class APIUtils {
  2. @DocumentedtAnnotation(key = "isM", value = "is M version")
  3. public static void isM() {
  4. }
  5. }
[/code]

然后利用javac, javadoc或者IDE提供的generate javadoc功能生成javadoc:

可以看到刚刚注解的内容。

 

 

7. 注解的性能问题​​​​​​​

注解得配合反射才能生效,少量的反射还好,但是如果项目中存在大量的反射,是会影响性能的,反射中的getMethod和getDeclaredField方法会比invoke和set方法更耗时。

自从EventBus 3.x发布之后其通过注解预编译的方式解决了之前通过反射机制所引起的性能效率问题,其中注解预编译所采用的的就是android-apt的方式,不过最近Apt工具的作者宣布了不再维护该工具了,因为Android Studio推出了官方插件,并且可以通过gradle来简单的配置,它就是annotationProcessor。

APT(Annotation Processing Tool)是一种处理注释的工具,它对源代码文件进行检测找出其中的Annotation,使用Annotation进行额外的处理。 Annotation处理器在处理Annotation时可以根据源文件中的Annotation生成额外的源文件和其它的文件(文件具体内容由Annotation处理器的编写者决定),APT还会编译生成的源文件和原来的源文件,将它们一起生成class文件。

 

内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: