Welcome to Ray's Blog

Stay Hungry Stay Foolish - Steve Jobs

0%

Java 注解机制笔记


注解概述

在 Java 中,注解(Annotation)引入始于 Java5,用来描述 Java 代码的元信息,通常情况下注解不会直接影响代码的执行,尽管我们使用一些特定类型的注解也可以达到这一目的。

Java Annotation Purposes

Java 注解通常用来达到以下的目的:

  • Compiler instruction :编译器说明
  • Build-time instruction:构建时说明
  • Runtime instruction:运行时说明

Java 已经内置了三种 Compiler instruction,见后文。

Java 注解可以应用在构建项目时。构建过程包括生成源码,编译源码,生成 xml 文件,打包编译的源码和文件到 jar 包等。软件的构建通常使用诸如 Apache Ant 和 Maven 这种工具自动完成(Android 中使用 Gradle 脚本语言)。这些构建工具会依照特定的注解扫描 Java 代码,然后根据这些注解生成源码或者文件。

通常情况下,注解并不会出现在编译后的代码中,但是如果想要出现也是可以的。Java 支持运行时的注解,使用 Java 的反射我们可以访问 到这些注解,运行时的注解的目的通常是提供给程序和第三方 API 的一些指令。

注解基础

一个简单的 Java 注解类似于@Entiry。其中@的意思就是告诉编译器这是一个注解。而Entity则是注解的名字。通常在文件中写法如下:

1
2
3
public @interface Entity{
//some filed
}

注解元素

Java 注解可以使用元素来进行设置一些值,注解中的元素类似于属性或者参数。定义包含元素的注解示例代码:

1
2
3
public @interface Entity{
String tableName();
}

使用包含元素的注解示例代码:

1
@Entity(tableName =  “vehicles”)

上述注解的元素名称称为tableName,设置的值为vehicles。没有元素的注解不需要使用括号。
如果包含多个元素,使用方法如下:

1
@Entity(tableName=“vehicles”,primaryKey="id"

如果注解只有一个元素,通常我们的写法是这样子的:

1
@InsertNes(value="yes")

但是这种情况下,当且仅当元素名为value,我们也可以简写,即不需要填写元素名value,效果如下:

1
@InsertNew("yes")

注解使用

注解可以用来修饰代码中的这些元素

  • 接口
  • 方法
  • 方法参数
  • 属性
  • 局部变量

举个栗子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
@Entity //类注解
public class Vehicles {
@Persistent//成员变量注解
protected String vehicleName;

@Getter//方法注解
public String getVehicleName() {
return vehicleName;
}

public void setVehicleName(@Optional /*参数注解*/ String vehicleName) {
this.vehicleName = vehicleName;
}

public List addVehicleNameToList(List vehicleNames) {
@Optional //局部变量注解
List localTableName = null;
if (localTableName == null) {
localTableName = new ArrayList();
}
localTableName.add(getVehicleName());
return localTableName;
}
}

Java 内置编译器说明注解

Java 中有三种内置注解,这些注解用来为编译器提供说明。

  • @Deprecated 表示被注解的类,方法,属性属于过时的或者被弃用的状态;
  • @Override 表示需要重写方法
  • @SuppressWarnings 表示不提示警告信息

创建自己的注解

在 Java 中,我们可以创建自己的注解,注解和类、接口文件一样定义在自己的文件里面。如下:

1
2
3
4
5
6
public @interface MyAnnotation {
String value();
String name();
int age();
String[] newNames();
}

自定义注解跟接口定义方法类似,都有类型和名称。这些类型可以是一下几种:

  • 原始数据类型
  • String
  • class
  • annotation
  • 枚举
  • 一维数组

如何使用:

1
2
3
4
5
6
7
@MyAnnotation(value = "123"
, name = "name"
, age = 22
, newNames = { "张三", "李四" })
public class MyClass {
//TODO some
}

** 注意:我们需要为所有的注解元素设置值,一个都不能少。除非设置了默认的元素值。**

注解元素默认值

对于注解中的元素,我们可以为其设置默认值,设置方式为在元素的后面添加default “”关键字+默认值。如下:

1
2
3
4
5
6
public @interface MyAnnotation {
String value() default000”;
String name() default “朵蜜天使”;
int age() default18”;
String[] newNames();
}

当我们使用带默认值的注解时,可以不用为有默认值的注解元素赋值,即让其使用默认值作为值。如下:

1
2
3
4
@MyAnnotation(newNames = { "张三", "李四" })
public class MyClass {
//TODO some
}

@Retention

@Retention是用来修饰注解的注解,使用这个注解,我们可以做到:

  • 控制注解是否写入 class 文件;
  • 控制 class 文件中的注解是否运行时可见。

控制很简单,其中包括三种策略:

  • RetentionPolicy.SOURCE 表明注解仅存在源码之中,不存在.class 文件中,更不能运行时可见。常见的注解为@Override,@SuppressWarnings.
  • RetentionPolicy.CLASS 这是默认的注解保留策略。这种策略下,注解将存在于.class 文件中,但是不能被运行时访问。通常这种注解策略用来处理一些字节码级别的操作。
  • RetentionPolicy.RUNTIME 这种策略下可以被运行时访问到。通常情况下,我们都会结合反射来做一些事情。

使用示例:

1
2
3
4
5
6
7
@Retention(RetentionPolicy.RUNTIME)
public @interface MyAnnotation {
String value() default "000";
String name();
int age();
String[] newNames();
}

@Target

使用@Target 注解,我们可以设定自定义注解可以修饰那些 java 元素。示例:

1
2
3
4
5
6
7
8
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface MyAnnotation {
String value() default "000";
String name();
int age();
String[] newNames();
}

上面的代码说明@MyAnnotation 注解只能修饰方法。
@Target() 参数值有:

  • ElementType.ANNOTATION_TYPE(注:修饰注解)
  • ElementType.CONSTRUCTOR //修饰构造方法
  • ElementType.FIELD //修饰属性
  • ElementType.LOCAL_VARIABLE//修饰变量
  • ElementType.METHOD//修饰方法
  • ElementType.PACKAGE//修饰包
  • ElementType.PARAMETER//修饰参数
  • ElementType.TYPE(注:任何类型,即上面的的类型都可以修饰)

@Inherited

如果你想让一个类和它的子类都包含某一个注解,就可以使用@Inherited 来修饰这个注解。示例:

1
2
3
4
5
6
7
8
9
@Retention(RetentionPolicy.RUNTIME)  //运行时注解
@Target(ElementType.METHOD) //只能用来修饰方法
@Inherited //子类继承注解
public @interface MyAnnotation {
String value() default "000";
String name();
int age();
String[] newNames();
}

@Docmented

添加@Docmented 注解,会将该注解在生成 java doc 的时候添加到 doc 文件内容中。

1
2
3
4
5
6
7
8
9
10
@Retention(RetentionPolicy.RUNTIME)  //运行时注解
@Target(ElementType.METHOD) //只能用来修饰方法
@Inherited //子类继承注解
@Documented
public @interface MyAnnotation {
String value() default "000";
String name();
int age();
String[] newNames();
}

Java 8 注解扩展

类型注解

  • 在原有范围上,扩展了注解的使用范围.
1
2
3
4
5
6
7
8
9
10
11
12
13
eg:
创建类实例
new@Interned MyObject();
类型映射
myString = (@NonNull String) str;
implements 语句中
class UnmodifiableList<T> implements@Readonly List<@Readonly T> { ... }
throw exception声明
void monitorTemperature() throws@Critical TemperatureException { ... }
Note:
在Java 8里面,当类型转化甚至分配新对象的时候,都可以在声明变量或者参数的时候使用注解。
Java注解可以支持任意类型。
类型注解只是语法而不是语义,并不会影响java的编译时间,加载时间,以及运行时间,也就是说,编译成class文件的时候并不包含类型注解。
  • 新增 ElementType.TYPE_USE 和 ElementType.TYPE_PARAMETER(在 Target 上)
1
2
ElementType.TYPE_PARAMETER 表示该注解能写在类型变量的声明语句中。
ElementType.TYPE_USE 表示该注解能写在使用类型的任何语句中(eg:声明语句、泛型和强制转换语句中的类型)。
  • 类型注解的作用
1
类型注解被用来支持在Java的程序中做强类型检查。配合第三方插件工具Checker Framework(注:此插件so easy,这里不介绍了),可以在编译的时候检测出runtime error(eg:UnsupportedOperationException; NumberFormatException;NullPointerException异常等都是runtime error),以提高代码质量。这就是类型注解的作用。

重复注解 @Repeatable

允许在同一声明类型(类,属性,或方法)上多次使用同一个注解。

Java8 以前的版本使用注解有一个限制是相同的注解在同一位置只能使用一次,不能使用多次。

Java 8 引入了重复注解机制,这样相同的注解可以在同一地方使用多次。重复注解机制本身必须用 @Repeatable 注解。

实际上,重复注解不是一个语言上的改变,只是编译器层面的改动,技术层面仍然是一样的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
eg:
1)自定义一个包装类Hints注解用来放置一组具体的Hint注解
[java] view plain copy print?
@interface MyHints {
Hint[] value();
}
@Repeatable(MyHints.class)
@interface Hint {
String value();
}
使用包装类当容器来存多个注解(旧版本方法)
[java] view plain copy print?
@MyHints({@Hint("hint1"), @Hint("hint2")})
class Person {}
使用多重注解(新方法)
[java] view plain copy print?
@Hint("hint1")
@Hint("hint2")
class Person {}

注解解析获取注解及注解元素值

资料

总结

参考资源 1

参考资源 2

参考资料 3