介绍
注解可以理解成一个标签,是给类、方法、变量、属性等加标签。注解(Annotation) 为我们在代码中添加信息提供了一种形式化的方法,是我们可以在稍后 某个时刻方便地使用这些数据(通过 解析注解 来使用这些数据),常见的作用有以下几种:
- 提供信息给编译器: 编译器可以利用注解来探测错误和警告信息
- 编译阶段时的处理: 软件工具可以用来利用注解信息来生成代码、Html文档或者做其它相应处理
- 运行时的处理: 某些注解可以在程序运行的时候接受代码的提取
元注解
『元注解』是用于修饰注解的注解,通常用在注解的定义上,例如:
JAVA 中有以下几个『元注解』:
- @Target:注解的作用目标
- @Retention:注解的生命周期
- @Documented:注解是否应当被包含在 JavaDoc 文档中
- @Inherited:是否允许子类继承该注解
@Target
@Target 注解指明了注解的使用范围。其包含一个 ElementType 数组类型的属性字段,属性名是 value。
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Target {
ElementType[] value();
}
ElementType 是一个枚举类型,其包含以下一些值:
public enum ElementType {
// 被修饰的注解可以作用在类、接口(包括注解类型)、或者枚举上
TYPE,
// 被修饰的注解可以作用在属性上
FIELD,
// 被修饰的注解可以作用在方法上
METHOD,
// 被修饰的注解可以作用在参数上
PARAMETER,
// 被修饰的注解可以作用在构造器上
CONSTRUCTOR,
// 被修饰的注解可以作用在本地局部变量上
LOCAL_VARIABLE,
// 被修饰的注解可以作用在注解上
ANNOTATION_TYPE,
// 被修饰的注解可以作用在包上
PACKAGE,
// 1.8新增类型,被修饰的注解可以作用在泛型上
TYPE_PARAMETER,
// 1.8新增类型,被修饰的注解可以作用在任何类型上
TYPE_USE
}
@Retention
Retention 的英文意为保留期的意思。当 @Retention 应用到一个注解上的时候,它解释说明了这个注解的的存活时间。
Retention 包含一个 RetentionPolicy 数组类型的属性字段,属性名是 value。
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Retention {
RetentionPolicy value();
}
RetentionPolicy 意为保留策略,是一个枚举类型。
public enum RetentionPolicy {
// 注解只在源码阶段保留,在编译器进行编译时它将被丢弃忽视。
SOURCE,
// 注解只被保留到编译进行的时候,它并不会被加载到 JVM 中
CLASS,
// 注解可以保留到程序运行的时候,它会被加载进入到 JVM 中,所以在程序运行时可以获取到它们
RUNTIME
}
@Documented
顾名思义,@Documented 是和文档有关。被它标记的元素会被 Javadoc 工具处理,作用是能够将注解中的元素包含到 Javadoc 中去。
@Inherited
Inherited 是继承的意思,其表明了注解的继承关系,子类可以继承父类的注解声明。
如果一个类 1 被 @Inherited 注解过,那么用注解 A 去标记类 1,类 2 继承自类 1。不管类 2 有没有注解 A,类 2 都有 A 的注解,其继承自类 1。
@Repeatable
Repeatable 是可重复的意思。@Repeatable 是 Java 1.8 才加进来的,所以算是一个新的特性。
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Repeatable {
// 包含一组相同类型的值
Class<? extends Annotation> value();
}
从上面的代码可以看出,@Repeatable 注解使用了泛型,其值是注解的 Class 类型,属性名是 value。
该注解的用法可以参考这篇博文,此处就不介绍了,还是很简单的.
# 注解的属性
从上面的代码中,我们知道注解的定义是使用 @interface。其实所有的注解都隐式继承自 Annotation 这个接口。所以我们见到的所有注解的属性,严格意义上讲并不是属性,而是方法。想想接口的方法是怎么定义的?
```java
public interface A {
// 接口的方法定义,加括号
String say();
}
public @interface B {
// 注解的属性定义,加括号
String say();
}
两者的定义方式是不是出奇的一致!!!所以,注解的属性,念做属性,实为方法。因此,注解中的属性定义时,一定要加上括号。
Java 内置注解
Java 的内置注解,相信我们大家都不陌生。包括 @Deprecated,@Override,@SuppressWarnings,@SafeVarargs,@FunctionalInterface。
@Deprecated
这个元素是用来标记过时的元素,编译器在编译阶段遇到这个注解时会发出提醒警告,告诉开发者正在调用一个过时的元素比如过时的方法、过时的类、过时的成员变量。比较鲜明的特征是,被这个注解标记的元素会被打上删除线。
@Override
这个元素提示子类要复写父类中被 @Override 修饰的方法。表明子类中被这个注解标记的方法均来自父类。该注解只可作用于方法。
@SuppressWarnings
这个注解用来抑制编译器的警告。之前说过调用被 @Deprecated 注解的方法后,编译器会警告提醒。而有时候开发者会忽略这种警告,他们可以在调用的地方通过 @SuppressWarnings 达到目的。
该注解的使用原理:Java 1.5 为 Java 增加了注解。使用时可以为 "javac" 指令增加 -Xlint 参数来控制是否报告这些警告(如@Deprecated)。默认情况下,Sun 编译器以简单的两行的形式输出警告。通过添加 -Xlint:keyword 标记(例如 -Xlint:finally),您可以获得关键字类型错误的完整说明。通过在关键字前面添加一个破折号,写为 -Xlint:-keyword,您可以取消警告。(-Xlint 支持的关键字的完整列表可以在 javac 文档页面上找到。)
下面是使用到的关键字的详细说明:
- deprecation:使用了不赞成使用的类或方法时的警告
- unchecked:执行了未检查的转换时的警告,例如使用集合时没有指定泛型类型
- fallthrough:当 Switch 程序块直接通往下一种情况而**没有 Break **时的警告
- path:在类路径、源文件路径等中有不存在的路径时的警告
- serial:当在可序列化的类上缺少 serialVersionUID 定义时的警告
- finally:任何 finally 子句不能正常完成时的警告
- all:关于以上所有情况的警告
基于上面的描述,下面是一个使用例子:
public class D1 {
@Deprecated
public static void foo() {
}
}
public class D2 {
// 忽略废弃元素的警告,value中的可指定值在上面列举出来了
@SuppressWarnings(value={"deprecation"})
public static void main(String[] args) {
D1.foo();
}
}
@SafeVarargs
参数安全类型注解。它的目的是提醒开发者不要用参数做一些不安全的操作。它的存在会阻止编译器产生 unchecked 这样的警告。它是在 Java 1.7 的版本中加入的。
具体的例子此处我就不讲了,可以看看这片博文
@FunctionalInterface
函数式接口注解,这个是 Java 1.8 版本引入的新特性。函数式编程很火,所以 Java 8 也及时添加了这个特性。函数式接口可以很容易转换为 Lambda 表达式。
Android 注解
Android 平台提供了部分注解供我们使用,在使用之前,需要导入相关的包,语句如下:
compile 'com.android.support:support-annotations:VERSION_NUM'
此包提供的注解如下:
IntDef 和 StringDef
IntDef 和 StringDef 是 Android 提供的两个魔法变量注解,用于取代 enum 和 魔法数字(字符串)等,其有一个数组类型的 value 属性。
假设我们需要定义消息类型,包括语音、文本、表情、图片、视频等,这些消息类型用整数定义。
public class Msg {
int msgType;
}
我们并不希望出现其他数字的消息类型,即需要限定类型的范围。此时就有几种方法:
- 在使用处加上注释,这并不是什么好方法,不建议采用
- 使用枚举类型代替整型,这种方法是可行的,但是在低端机上可以会存在内存占用大的情况。其使用方法如下:
public class Msg {
MsgType msgType;
}
public enum MsgType {
TEXT, VOICE, PHOTO, VIDEO
}
- 第三种方法便是使用注解,可以使用 IntDef 注解。其使用方法如下:
// 1、定义消息类型
public class MsgType {
public static final int TEXT = 1;
public static final int VOICE = 2;
public static final int PHOTO = 3;
public static final int VIDEO = 4;
}
// 2、自定义注解,指定取值范围,后续增加的消息类型都需要在这里加入
// IntDef 有一个属性 value 是一个 int 类型的数组
@IntDef(value = {MsgType.TEXT, MsgType.VOICE, MsgType.PHOTO, MsgType.VIDEO})
@Retention(RetentionPolicy.SOURCE)
public @interface AMsgType {
}
// 3、使用注解
// 这样就指定了取值范围
public class Test {
@AMsgType
public int msgType;
}
就这么简单,StringDef 的用法也差不多。不过需要注意的是,IntDef 注解还有个 boolean 类型的 flag 的属性,默认情况下,IntDef 的所有取值会被当作 enum (枚举类型)处理,而如果设置了 flag = true,那么 IntDef 中设置的取值范围就会被当作 flag (标志位)处理,标志位可以进行位运算。
另外,有一个 IntRange 注解和 IntDef 注解类似,前者可以指定取值范围,包括 from 和 to 两个属性,取值区间是连续的。而后者的取值范围更自由,可以不是连续的取值范围。
Threading 注解
Thread注解是帮助我们设置方法的执行线程,如果和指定的线程不一致,抛出异常。Threading 注解类型:
- @UiThread:UI线程
- @MainThread:主线程
- @WorkerThread:工作线程(子线程)
- @BinderThread:绑定线程
取值限制范围注解
Android 提供了集中取值的范围限制注解供我们使用,其中就包括上面我们提到的 @IntRange 和 @FloatRange 注解,另外还包括一个 @Size 注解。常用的就这三个:
- @Size
- @IntRange
- @FloatRange
@Size 使用 min、max,而后两者使用 from、to。@Size 用于定义尺寸,其源码如下:
@Retention(SOURCE)
@Target({PARAMETER,LOCAL_VARIABLE,METHOD,FIELD})
public @interface Size {
// 尺寸的默认值
long value() default -1;
// 尺寸 n 的取值范围:min <= n <= max
// 可取的最小值
long min() default Long.MIN_VALUE;
// 可取的最大值
long max() default Long.MAX_VALUE;
// 尺寸的缩放倍数
long multiple() default 1;
}
@CallSuper 注解
@CallSuper 注解主要是用来提示子类在覆盖父类的方法时,需要调用对应的super.***方法。下面的代码是个例子:
// 父类
public class F {
// 提示子类在重写父类的 init 方法时,需要加上 super.init 语句
@CallSuper
protected void init(){
}
}
// 子类
class T extends F{
@Override
protected void init() {
// 此处不加入这句,编译器会报错
super.init();
}
}
@CheckResult 注解
此注解主要是提示我们使用到方法定义的返回值。该注解有一个 suggest 的字符串类型的属性,允许我们加上一些提醒,来告诉方法调用者为什么要使用此方法的返回值。
@CheckResult(suggest = "这是注解的说明,请使用返回值!!!")
public int getInt() {
return 1;
}
public static void main(String[] args) {
// 这种调用方式会报错
getInt();
// 这种调用方法是正确的
int result = getInt();
}
另外,注解也可以通过反射获取到。