探索注解之注解的基本概念

自从 Java 5.0 版本引入注解后,它就成为 Java 平台中非常重要的一部分。在日常开发过程中时常可以看到 @Override,@Deprecated 这样的注解,在使用 Butterknife 减少 findViewById 这样的重复劳动时也经常用到 @BindView 注解。下面几篇文章将讲述注解的基本概念,注解处理器的使用以及 Android中 的注解:

什么是注解?

注解(Annotations),就是元数据,一种描述程序代码的数据,并且对程序运行没有任何影响。

例如下面这段代码:

1
2
3
4
@Override
public String toString() {
return "This is String Representation of current object";
}

在上面的代码中,我重写了 toString() 方法并使用了 @Override 注解。但是即便我不使用 @Override 注解标记代码,程序也能正常运行。那么这个注解有什么意义?@Override 告诉编译器这个方法是重写父类的方法,如果父类中没有该方法,编译器就会报错,提示没有重写父类中的方法。如果我不小心把 toString 写成 toStrings,而且也没有使用 @Override 注解的话,程序依然可以运行,但是跟之前期望就会大有不同。

注解有下面几种作用:

  • 为编译器提供信息 – 注解可以被编译器用来检测错误或抑制警告
  • 编译时和部署时处理 – 一些工具可以根据注解信息生成代码,XML 文件等等,例如 APT(Annotation Processing Tool)
  • 运行时处理 – 有些注解可以在运行时通过反射获取

在引入注解之前,开发人员通常使用标记 interfaces,注释或者 javadoc 定义元数据,没有统一的一种方式。注解定义了一种标准的描述元数据的方式,注解是一种应用于类、方法、参数、变量、构造器及包声明中的特殊修饰符。它是一种由 JSR-175 标准选择用来描述元数据的一种工具。

Java 内置注解

在了解注解的定义后,先看看我们熟悉的 Java 内置的注解:

@Deprecated – 意味着标记的元素这已经弃用了,如果使用了 @Deprecated 标记的元素编译器会提示警告。Java 推荐在 Javadoc 中提供弃用的原因并提供替代的方案.

Date 的 pare 方法

1
2
3
4
5
6
7
8
9
10
11
12
13
/**
* Returns the millisecond value of the date and time parsed from the
* specified {@code String}. Many date/time formats are recognized, including IETF
* standard syntax, i.e. Tue, 22 Jun 1999 12:16:00 GMT-0500
*
* @param string
* the String to parse.
* @return the millisecond value parsed from the String.
*
* @deprecated Use {@link DateFormat} instead.
*/
@Deprecated
public static long parse(String string)

@Override – 标记该方法是重写父类的,当父类的该方法移除或修改时,编译器会提供错误信息。

@SuppressWarnings – 告诉编译器忽略某些特定的警告。

1
@SuppressWarnings({"unchecked", "deprecation"})

@SafeVarargs – 忽略”堆污染”警告,Java 7 引入的

@FunctionalInterface – Java 8 引入的,用来表示接口是函数式接口

元注解

在上面 Java 自带注解或者自定义注解时,会发现应用于注解本身的注解,这就是元注解。这些元注解都定义在java.lang.annotation包中。

@Retention – 定义了注解的生命周期

  • RetentionPolicy.SOURCE - 只能在源文件中保留,被编译器丢弃

  • RetentionPolicy.CLASS - 可以在 .Class 文件中保留,被 JVM 丢弃,这也是默认值

  • RetentionPolicy.RUNTIME - 可以在运行时保留

@Documented – 可以使用 javadoc 或类似工具文档化

@Target – 指定注解可修饰的元素范围,即可以标记在哪里(方法上、类上等等),如果不使用 @Target 注解,可以修饰任何元素。下面是取值列表:

  • ElementType.ANNOTATION_TYPE - 描述其他注解

  • ElementType.CONSTRUCTOR - 描述构造器

  • ElementType.FIELD - 描述类属性(包括 enum 常量)

  • ElementType.LOCAL_VARIABLE - 描述局部变量

  • ElementType.METHOD - 描述方法

  • ElementType.PACKAGE - 描述包

  • ElementType.PARAMETER - 描述参数

  • ElementType.TYPE - 描述类、接口(包括注解)、枚举 enum

@Inherited – 表明被标注的注解可被子类继承

@Repeatable – Java 8 引入,表明被标记的注解可以在一个声明或类型中使用多次

自定义注解

自定义注解有点类似写接口,只是interface关键字需要前缀@。我们可以声明一个描述类属性的注解,下面是具体例子:

1
2
3
4
5
6
7
8
@Documented
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Column {
String name(); // 定义 name 属性
String type(); // 定义 type 属性
boolean isPrimaryKey() default false; // 定义 isPrimaryKey 属性,默认值为 false。
}

上面定义了 @Column 注解,有三个成员变量,成员变量的声明类似无参方法的声明,定义注解的规则如下:

  • 注解可以用元注解标记,指定可应用的元素类型,或确定生命周期等

  • 注解的属性用类似无参方法的声明,不能带有任何参数

  • 注解的属性类型只能是基本数据类型、String、Enum、注解以及相应的数组

  • 注解的属性可以有默认值,非基本数据类型的值不能为null

  • 如果只有一个属性,建议命名为 “value”,使用时就无需标明属性名

Java 注解解析(运行时)

上面我们已经定义了 @Column 注解,下面是注解使用的例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
public final class Bookmarks {
private Bookmarks() {}
@Column(name = "_id", type = "INTEGER", isPrimaryKey = true)
public static final String ID = "_id";
@Column(name = "title", type = "TEXT")
public static final String TITLE = "title";
@Column(name = "url", type = "TEXT")
public static final String URL = "url";
}

之前说过注解不会影响代码的运行,因此必须配合相应的注解处理工具,否则注解跟注释没什么分别。根据 @Retention 指定的值不同,有不同的处理方式。声明为 RetentionPolicy.CLASS 时,可以用 javac 内置的 APT(Annotation Processing Tool)在编译时扫描处理注解,而声明为 RetentionPolicy.RUNTIME 时,可以用反射 API 的 AnnotatedElement 接口解析注解。

下面的代码示例是用反射解析上面 Bookmarks 类中的注解:

1
2
3
4
5
6
7
public static void parseColumnAnnotation() {
Field[] fields = Bookmarks.class.getDeclaredFields();
for (Field field : fields) {
Column column = field.getAnnotation(Column.class);
System.out.println("column name = " + column.name() + " type = " + column.type() + " isPrimaryKey = " + column.isPrimaryKey());
}
}

运行结果如下:

1
2
3
column name = _id type = INTEGER isPrimaryKey = true
column name = title type = TEXT isPrimaryKey = false
column name = url type = TEXT isPrimaryKey = false

参考文章: