0%

Improve Code Inspection with Annotations

  1. What’s Annotation?
  2. Why use Annotation?
  3. How to Use Annotation?
  4. Pitfall
  5. Best Practice

What’s Annotation?

Annotation

@interface

@interface是java中用于声明注解类的关键字.使用该注解表示将自动继承java.lang.annotation.Annotation类,该过程交给编译器完成.需要注意:在定义注解时,不能继承其他注解或接口.

元注解:
元注解的作用就是负责注解其他注解。Java5.0定义了4个标准的meta-annotation类型,它们被用来提供对其它 annotation类型作说明。Java5.0定义的元注解:
@Target,
@Retention,
@Documented,
@Inherited

@Target:

该注解用于定义注解的作用目标,即注解可以用在什么地方,比如是用于方法上还是用于字段上,该注解接受以下参数:

作用目标 含义
@Target(ElementType.TYPE) 用于接口(注解本质上也是接口),类,枚举
@Target(ElementType.FIELD) 用于字段,枚举常量
@Target(ElementType.METHOD) 用于方法
@Target(ElementType.PARAMETER) 用于方法参数
@Target(ElementType.CONSTRUCTOR) 用于构造参数
@Target(ElementType.LOCAL_VARIABLE) 用于局部变量
@Target(ElementType.ANNOTATION_TYPE) 用于注解
@Target(ElementType.PACKAGE) 用于包

,通过@interface,@Retention,@Target已经可以完整的定义一个注解

@Retention:

对Annotation的“生命周期”限制:某些Annotation仅出现在源代码中,而被编译器丢弃;而另一些却被编译在class文件中;编译在class文件中的Annotation可能会被虚拟机忽略,而另一些在class被装载时将被读取。
作用:表示需要在什么级别保存该注释信息,用于描述注解的生命周期(即:被描述的注解在什么范围内有效)
RetentionPolicy.SOURCE:在源文件中有效
RetentionPolicy.CLASS:在class文件中有效
RetentionPolicy.RUNTIME:在运行时有效

1
2
3
4
5
6
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Column {
public String name() default "fieldName";
public boolean defaultDBValue() default false;
}

Column注解的的RetentionPolicy的属性值是RUTIME,这样注解处理器可以通过反射,获取到该注解的属性值,从而去做一些运行时的逻辑处理。

@Documented:

该注解用于描述其它类型的annotation应该被javadoc文档化,出现在api doc中.
比如使用该注解的@Target会出出现在api说明中.

1
2
3
4
5
6
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Target {
ElementType[] value();
}

@Inherited:

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

注意:当@Inherited的Retention是RetentionPolicy.RUNTIME,则反射API增强了这种继承性。如果我们使用java.lang.reflect去查询一个@Inherited annotation类型的annotation时,反射代码检查将展开工作:检查class和其父类,直到发现指定的annotation类型被发现。

1
2
3
4
5
6
@Inherited
public @interface Greeting {
public enum FontColor{ BULE,RED,GREEN};
String name();
FontColor fontColor() default FontColor.GREEN;
}

注解参数

参数成员只能用基本类型byte,short,char,int,long,float,double,boolean八种基本数据类型和 String,Enum,Class,annotations等数据类型,以及这一些类型的数组。

默认值

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

1
2
3
4
5
6
7
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface FruitProvider {
public int id() default -1;
public String name() default "";
public String address() default "";
1
2
3
4
5
6
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface FruitProvider {
public int value() default -1;
}

系统注解

java设计者已经为我们自定义了几个常用的注解,我们称之为系统注解,主要是这三个:

系统注解 含义
@Override 用于修饰方法,表示此方法重写了父类方法
像@Override这样,没有成员定义的注解称之为标记注解.

@Deprecated 用于修饰方法,表示此方法已经过时
需要注意@Deprecated和@deprecated这两者的区别,前者被javac识别和处理,而后者则是被javadoc工具识别和处理.因此当我们需要在源码标记某个方法已经过时应该使用@Deprecated,如果需要在文档中说明则使用@deprecated

@SuppressWarnnings 该注解用于告诉编译器忽视某类编译警告
该注解被用于有选择的关闭编译器对类,方法,成员变量即变量初始化的警告.该注解可接受以下参数:

参数 含义
deprecated 使用已过时类,方法,变量
unchecked 执行了未检查的转告时的警告,如使用集合是为使用泛型来制定集合保存时的类型
fallthrough 使用switch,但是没有break时
path 类路径,源文件路径等有不存在的路径
serial 可序列化的类上缺少serialVersionUID定义时的警告
finally 任何finally字句不能正常完成时的警告
all 以上所有情况的警告

自定义注解

了解完系统注解之后,现在我们就可以自己来定义注解了,通过上面@Override的实例,不难看出定义注解的格式如下:

public @interface 注解名 {定义体}

定义体就是方法的集合,每个方法实则是声明了一个配置参数.方法的名称作为配置参数的名称,方法的返回值类型就是配置参数的类型.和普通的方法不一样,可以通过default关键字来声明配置参数的默认值.

需要注意:

  1. 此处只能使用public或者默认的default两个权限修饰符
  2. 配置参数的类型只能使用基本类型(byte,boolean,char,short,int,long,float,double)和String,Enum,Class,annotation
  3. 对于只含有一个配置参数的注解,参数名建议设置中value,即方法名为value
  4. 配置参数一旦设置,其参数值必须有确定的值,要不在使用注解的时候指定,要不在定义注解的时候使用default为其设置默认值,对于非基本类型的参数值来说,其不能为null.
    像@Override这样,没有成员定义的注解称之为标记注解.

注解处理器

根据注解的特性,注解处理器可以分为运行时注解处理编译时注解处理器.运行时处理器需要借助反射机制实现,而编译时处理器则需要借助APT来实现.

Why use Annotation?

Annotations allow you to provide hints to code inspections tools like Lint, to help detect these more subtle code problems. They are added as metadata tags that you attach to variables, parameters, and return values to inspect method return values, passed parameters, local variables, and fields. When used with code inspections tools, annotations can help you detect problems, such as null pointer exceptions and resource type conflicts.

How to Use Annotation?

Android supports a variety of annotations through the Annotations Support Library. You can access the library through the android.support.annotation package.

Nullness Annotations

线程注解: @UiThread, @WorkerThread, …

(Support library 22.2及其之后版本支持.)

如果你的方法只能在指定的线程类型中被调用,那么你就可以使用以下4个注解来标注它:
@UiThread
@MainThread
@WorkerThread
@BinderThread
如果一个类中的所有方法都有相同的线程需求,那么你可以注解类本身。比如android.view.View,就被用@UiThread标注。

关于线程注解使用的一个很好的例子就是AsyncTask:

1
2
3
4
5
6
@WorkerThread
protected abstract Result doInBackground(Params... params);

@MainThread
protected void onProgressUpdate(Progress... values) {
}

@UiThread还是@MainThread?

在进程里只有一个主线程。这个就是@MainThread。同时这个线程也是一个@UiThread。比如activity的主要窗口就运行在这个线程上。然而它也有能力为应用创建其他线程。这很少见,一般具备这样功能的都是系统进程。通常是把和生命周期有关的用@MainThread标注,和View层级结构相关的用@UiThread标注。但是由于@MainThread本质上是一个@UiThread,而大部分情况下@UiThread又是一个@MainThread,所以工具(lint ,Android Studio,等等)可以把他们互换,所以你能在一个可以调用@MainThread方法的地方也能调用@UiThread方法,反之亦然。

值约束: @Size, @IntRange, @FloatRange

Pitfall

Best Practice

Reference

[1]Improve Code Inspection with Annotations
[2]Android注解支持(Support Annotations)
[3]性能最佳实践: 在开发中避免使用枚举
[4]Java Annotation最佳入门实践
[5]Android APT(编译时代码生成)最佳实践
[6]ActivityRouter路由框架:通过注解实现URL打开Activity
[7]基础篇:带你从头到尾玩转注解
[8]拓展篇:注解处理器最佳实践
[9]Java深度历险(六)——Java注解
[10]Java Annotation 及几个常用开源项目注解原理简析

欢迎关注我的其它发布渠道