你不知道的 Java 注解那些事!

程序猿杂货铺

共 2051字,需浏览 5分钟

 ·

2019-08-24 10:43


戳上方蓝字 “程序猿杂货铺” 关注我 并 置顶星标

你的关注意义重大!

原文 | http://1t.click/JpS


注解对于开发人员来讲既熟悉又陌生,熟悉是因为只要你是做开发,都会用到注解(常见的 @Override),陌生是因为即使不使用注解也照常能够进行开发,注解不是必须的,但了解注解有助于我们深入理解某些第三方框架(比如 Android Support Annotations、JUnit、xUtils、ActiveAndroid 等),提高工作效率。

Java 注解又称为标注,是 Java 从 1.5 开始支持加入源码的特殊语法元数据:Java中的类、方法、变量、参数、包都可以被注解。这里提到的元数据是描述数据的数据,结合实例来说明:

  1. name="app_name">AnnotionDemo

这里的 "app_name" 就是描述数据 "AnnotionDemo" 的数据,这是在配置文件中写的,注解是在源码中写的,如下所示:

  1. @Override

  2. protectedvoid onCreate(Bundle savedInstanceState){

  3. super.onCreate(savedInstanceState);

  4. setContentView(R.layout.activity_main_layout);

  5. newThread(newRunnable(){

  6. @Override

  7. publicvoid run(){

  8. setTextInOtherThread();

  9. }

  10. }).start();

  11. }

在上面的代码中,在 MainActivity.java 中复写了父类 Activity.java 的 onCreate 方法,使用到了 @Override 注解。但即使不加上 @Override 注解标记代码,程序也能够正常运行。那这里的 @Override 注解有什么用呢?使用它有什么好处?事实上,@Override 是告诉编译器这个方法是一个重写方法,如果父类中不存在该方法,编译器会报错,提示该方法不是父类中的方法。如果不小心拼写错误,将 onCreate 写成了 onCreat,而且没有使用@Override 注解,程序依然能够编译通过,但运行结果和期望的大不相同。从示例可以看出,注解有助于阅读代码

使用注解很简单,根据注解类的 @Target 所修饰的对象范围,可以在类、方法、变量、参数、包中使用 “@+注解类名 + [属性值]” 的方式使用注解。比如:

  1. @UiThread

  2. privatevoid setTextInOtherThread(@StringResint resId){

  3. TextView threadTxtView =(TextView)MainActivity.this.findViewById(R.id.threadTxtViewId);

  4. threadTxtView.setText(resId);

  5. }

特别说明:

  • 注解仅仅是元数据,和业务逻辑无关,所以当你查看注解类时,发现里面没有任何逻辑处理;

  • javadoc 中的 @author、@version、@param、@return、@deprecated、@hide、@throws、@exception、@see 是标记,并不是注解;


注解的作用


  • 格式检查:告诉编译器信息,比如被 @Override 标记的方法如果不是父类的某个方法,IDE 会报错;

  • 减少配置:运行时动态处理,得到注解信息,实现代替配置文件的功能;

  • 减少重复工作:比如第三方框架 xUtils,通过注解 @ViewInject 减少对 findViewById 的调用,类似的还有(JUnit、ActiveAndroid 等);


注解是如何工作的?


注解仅仅是元数据,和业务逻辑无关,所以当你查看注解类时,发现里面没有任何逻辑处理,eg:

  1. @Target(ElementType.FIELD)

  2. @Retention(RetentionPolicy.RUNTIME)

  3. public@interfaceViewInject{


  4. int value();


  5. /* parent view id */

  6. int parentId()default0;

  7. }

如果注解不包含业务逻辑处理,必然有人来实现这些逻辑。注解的逻辑实现是元数据的用户来处理的,注解仅仅提供它定义的属性(类/方法/变量/参数/包)的信息,注解的用户来读取这些信息并实现必要的逻辑。当使用java中的注解时(比如 @Override、@Deprecated、@SuppressWarnings)JVM 就是用户,它在字节码层面工作。如果是自定义的注解,比如第三方框架ActiveAndroid,它的用户是每个使用注解的类,所有使用注解的类都需要继承 Model.java,在 Model.java 的构造方法中通过反射来获取注解类中的每个属性:

  1. publicTableInfo(ClassextendsModel> type){

  2. mType = type;


  3. finalTable tableAnnotation = type.getAnnotation(Table.class);


  4. if(tableAnnotation !=null){

  5. mTableName = tableAnnotation.name();

  6. mIdName = tableAnnotation.id();

  7. }

  8. else{

  9. mTableName = type.getSimpleName();

  10. }


  11. // Manually add the id column since it is not declared like the other columns.

  12. Field idField = getIdField(type);

  13. mColumnNames.put(idField, mIdName);


  14. List<Field> fields =newLinkedList<Field>(ReflectionUtils.getDeclaredColumnFields(type));

  15. Collections.reverse(fields);


  16. for(Field field : fields){

  17. if(field.isAnnotationPresent(Column.class)){

  18. finalColumn columnAnnotation = field.getAnnotation(Column.class);

  19. String columnName = columnAnnotation.name();

  20. if(TextUtils.isEmpty(columnName)){

  21. columnName = field.getName();

  22. }


  23. mColumnNames.put(field, columnName);

  24. }

  25. }


  26. }


注解和配置文件的区别


通过上面的描述可以发现,其实注解干的很多事情,通过配置文件也可以干,比如为类设置配置属性;但注解和配置文件是有很多区别的,在实际编程过程中,注解和配置文件配合使用在工作效率、低耦合、可拓展性方面才会达到权衡。

配置文件:

使用场合:

  • 外部依赖的配置,比如 build.gradle 中的依赖配置;

  • 同一项目团队内部达成一致的时候;

  • 非代码类的资源文件(比如图片、布局、数据、签名文件等);

优点:

  • 降低耦合,配置集中,容易扩展,比如 Android 应用多语言支持;

  • 对象之间的关系一目了然,比如 strings.xml;

  • xml 配置文件比注解功能齐全,支持的类型更多,比如 drawable、style等;

缺点:

  • 繁琐;

  • 类型不安全,比如 R.java 中的都是资源 ID,用 TextView 的 setText 方法时传入 int 值时无法检测出该值是否为资源 ID,但 @StringRes 可以;

注解:

使用场合:

  • 动态配置信息;

  • 代为实现程序逻辑(比如 xUtils 中的 @ViewInject 代为实现 findViewById);

  • 代码格式检查,比如 Override、Deprecated、NonNull、StringRes 等,便于 IDE 能够检查出代码错误;

优点:

  • 在 class 文件中,提高程序的内聚性;

  • 减少重复工作,提高开发效率,比如 findViewById。

缺点:

  • 如果对 annotation 进行修改,需要重新编译整个工程;

  • 业务类之间的关系不如 XML 配置那样一目了然;

  • 程序中过多的 annotation,对于代码的简洁度有一定影响;

  • 扩展性较差;


自定义注解


通过阅读注解类的源码可以发现,任何一个注解类都有如下特征:

  • 注解类会被 @interface 标记;

  • 注解类的顶部会被 @Documented、@Retention、@Target、@Inherited 这四个注解标记(@Documented、@Inherited可选,@Retention、@Target必须要有);

@UiThread 源码:

  1. @Documented

  2. @Retention(CLASS)

  3. @Target({METHOD,CONSTRUCTOR,TYPE})

  4. public@interfaceUiThread{

  5. }

元注解

上文提到的四个注解:@Documented、@Retention、@Target、@Inherited就是元注解,它们的作用是负责注解其它注解,主要是描述注解的一些属性,任何注解都离不开元注解(包括元注解自身,通过元注解可以自定义注解),元注解的用户是 JDK,JDK 已经帮助我们实现了这四个注解的逻辑。这四个注解在 JDK 的java.lang.annotation 包中。对每个元注解的详细说明如下:

@Target


作用:用于描述注解的使用范围,即被描述的注解可以用在什么地方;

取值:

1、CONSTRUCTOR:构造器;

2、FIELD:实例;

3、LOCAL_VARIABLE:局部变量;

4、METHOD:方法;

5、PACKAGE:包;

6、PARAMETER:参数;

7、TYPE:类、接口(包括注解类型) 或enum声明。

示例:

  1. /***

  2. *

  3. * 实体注解接口

  4. */

  5. @Target(value ={ElementType.TYPE})

  6. @Retention(value =RetentionPolicy.RUNTIME)

  7. public@interfaceEntity{

  8. /***

  9. * 实体默认firstLevelCache属性为false

  10. * @return boolean

  11. */

  12. boolean firstLevelCache()defaultfalse;

  13. /***

  14. * 实体默认secondLevelCache属性为false

  15. * @return boolean

  16. */

  17. boolean secondLevelCache()defaulttrue;

  18. /***

  19. * 表名默认为空

  20. * @return String

  21. */

  22. String tableName()default"";

  23. /***

  24. * 默认以""分割注解

  25. */

  26. String split()default"";

  27. }


@Retention


作用:表示需要在什么级别保存该注解信息,用于描述注解的生命周期,即被描述的注解在什么范围内有效;

取值:

1、SOURCE:在源文件中有效,即源文件保留;

2、CLASS:在class文件中有效,即class保留;

3、RUNTIME:在运行时有效,即运行时保留;

示例:

  1. /***

  2. * 字段注解接口

  3. */

  4. @Target(value ={ElementType.FIELD})//注解可以被添加在实例上

  5. @Retention(value =RetentionPolicy.RUNTIME)//注解保存在JVM运行时刻,能够在运行时刻通过反射API来获取到注解的信息

  6. public@interfaceColumn{

  7. String name();//注解的name属性

  8. }


@Documented


作用:用于描述其它类型的 annotation 应该被作为被标注的程序成员的公共 API,因此可以被例如 javadoc 此类的工具文档化。

取值:它属于标记注解,没有成员;

示例:

  1. @Documented

  2. @Retention(CLASS)

  3. @Target({METHOD,CONSTRUCTOR,TYPE})

  4. public@interfaceUiThread{

  5. }


@Inherited


作用:用于描述某个被标注的类型是可被继承的。如果一个使用了 @Inherited 修饰的 annotation 类型被用于一个 class,则这个 annotation 将被用于该class的子类。

取值:它属于标记注解,没有成员;

示例:

  1. /**

  2. * @author wangsheng

  3. **/

  4. @Inherited

  5. public@interfaceGreeting{

  6. publicenumFontColor{ BULE,RED,GREEN};

  7. String name();

  8. FontColor fontColor()defaultFontColor.GREEN;

  9. }


如何自定义注解

使用 @interface 自定义注解时,自动继承了 java.lang.annotation.Annotation 接口,由编译程序自动完成其他细节。在定义注解时,不能继承其他的注解或接口。@interface 用来声明一个注解,其中的每一个方法实际上是声明了一个配置参数。方法的名称就是参数的名称,返回值类型就是参数的类型(返回值类型只能是基本类型、Class、String、enum)。可以通过 default 来声明参数的默认值。

自定义注解格式


  1. 元注解

  2. public@interface注解名{

  3. 定义体;

  4. }


注解参数可支持的数据类型


1、所有基本数据类型(int,float,boolean,byte,double,char,long,short);

2、String 类型;

3、Class 类型;

4、enum 类型;

5、Annotation 类型;

6、以上所有类型的数组。


特别说明:

1、注解类中的方法只能用 public 或者默认这两个访问权修饰,不写 public 就是默认,eg:

  1. @Target(ElementType.FIELD)

  2. @Retention(RetentionPolicy.RUNTIME)

  3. @Documented

  4. public@interfaceFruitColor{

  5. publicenumColor{ BULE,RED,GREEN};

  6. Color fruitColor()defaultColor.GREEN;

  7. }

2、如果注解类中只有一个成员,最好把方法名设置为"value",比如:

  1. @Target(ElementType.FIELD)

  2. @Retention(RetentionPolicy.RUNTIME)

  3. @Documented

  4. public@interfaceFruitName{

  5. String value()default"";

  6. }

3、注解元素必须有确定的值,要么在定义注解的默认值中指定,要么在使用注解时指定,非基本类型的注解元素的值不可为 null。因此, 使用空字符串或0作为默认值是一种常用的做法。

实例演示


ToDo.java:注解类

  1. @Target(ElementType.METHOD)

  2. @Retention(RetentionPolicy.RUNTIME)

  3. @interfaceTodo{

  4. publicenumPriority{LOW, MEDIUM, HIGH}

  5. publicenumStatus{STARTED, NOT_STARTED}

  6. String author()default"Yash";

  7. Priority priority()defaultPriority.LOW;

  8. Status status()defaultStatus.NOT_STARTED;

  9. }

BusinessLogic:使用注解的类

  1. publicclassBusinessLogic{

  2. publicBusinessLogic(){

  3. super();

  4. }


  5. publicvoid compltedMethod(){

  6. System.out.println("This method is complete");

  7. }


  8. @Todo(priority =Todo.Priority.HIGH)

  9. publicvoid notYetStartedMethod(){

  10. // No Code Written yet

  11. }


  12. @Todo(priority =Todo.Priority.MEDIUM, author ="Uday", status =Todo.Status.STARTED)

  13. publicvoid incompleteMethod1(){

  14. //Some business logic is written

  15. //But its not complete yet

  16. }


  17. @Todo(priority =Todo.Priority.LOW, status =Todo.Status.STARTED )

  18. publicvoid incompleteMethod2(){

  19. //Some business logic is written

  20. //But its not complete yet

  21. }

  22. }

TodoReport.java:解析注解信息

  1. publicclassTodoReport{

  2. publicTodoReport(){

  3. super();

  4. }


  5. publicstaticvoid main(String[] args){

  6. getTodoReportForBusinessLogic();

  7. }


  8. /**

  9. * 解析使用注解的类,获取通过注解设置的属性

  10. */

  11. privatestaticvoid getTodoReportForBusinessLogic(){

  12. Class businessLogicClass =BusinessLogic.class;

  13. for(Method method : businessLogicClass.getMethods()){

  14. Todo todoAnnotation =(Todo)method.getAnnotation(Todo.class);

  15. if(todoAnnotation !=null){

  16. System.out.println(" Method Name : "+ method.getName());

  17. System.out.println(" Author : "+ todoAnnotation.author());

  18. System.out.println(" Priority : "+ todoAnnotation.priority());

  19. System.out.println(" Status : "+ todoAnnotation.status());

  20. System.out.println(" --------------------------- ");

  21. }

  22. }

  23. }

  24. }

执行结果如下图所示:

c68d1ec24fd9afea31373af669078abd.webp

推荐阅读


0e86e6e93672a4f1fd09af88d507336d.webp

0c26eda724278c2a0a83d62195346ecc.webp

0cab3f29b7d1092396c19ae35cee66d3.webp


如果这篇文章对你有帮助

请帮忙转发,点个在看

浏览 31
点赞
评论
收藏
分享

手机扫一扫分享

分享
举报
评论
图片
表情
推荐
点赞
评论
收藏
分享

手机扫一扫分享

分享
举报