前面写了Android 开发:由模块化到组件化(一),很多小伙伴来问怎么没有Demo啊?之所以没有立刻放demo的原因在还有许多技术点没说完.
今天我们就来细细评味Java当中Annotation,也就是我们常说的注解.
本文按照以下顺序进行:元数据->元注解->运行时注解->编译时注解处理器.
什么是元数据(metadata)
元数据由metadata译来,所谓的元数据就是“关于数据的数据”,更通俗的说就是描述数据的数据,对数据及信息资源的描述性信息.比如说一个文本文件,有创建时间,创建人,文件大小等数据,这都可以理解为是元数据.
在java中,元数据以标签的形式存在java代码中,它的存在并不影响程序代码的编译和执行,通常它被用来生成其它的文件或运行时知道被运行代码的描述信息。java当中的javadoc和注解都属于元数据.
什么是注解(Annotation)?
注解是从java 5.0开始加入,可以用于标注包,类,方法,变量等.比如我们常见的@Override,再或者Android源码中的@hide,@systemApi,@privateApi等
对于@Override,多数人往往都是知其然而不知其所以然,今天我就来聊聊Annotation背后的秘密,开始正文.
元注解
元注解就是定义注解的注解,是java提供给我们用于定义注解的基本注解.在java.lang.annotation包中我们可以看到目前元注解共有以下几个:
- @Retention
- @Target
- @Inherited
- @Documented
- @interface
下面我们将集合@Override注解来解释着5个基本注解的用法.
@interface
@interface是java中用于声明注解类的关键字.使用该注解表示将自动继承java.lang.annotation.Annotation类,该过程交给编译器完成.
因此我们想要定义一个注解只需要如下做即可,以@Override注解为例
public @interface Override {
}
需要注意:在定义注解时,不能继承其他注解或接口.
@Retention
@Retention:该注解用于定义注解保留策略,即定义的注解类在什么时候存在(源码阶段 or 编译后 or 运行阶段).该注解接受以下几个参数:RetentionPolicy.SOURCE,RetentionPolicy.CLASS,RetentionPolicy.RUNTIME
,其具体使用及含义如下:
注解保留策略 | 含义 |
---|---|
@Retention(RetentionPolicy.SOURCE) | 注解仅在源码中保留,class文件中不存在 |
@Retention(RetentionPolicy.CLASS) | 注解在源码和class文件中都存在,但运行时不存在,即运行时无法获得,该策略也是默认的保留策略 |
@Retention(RetentionPolicy.RUNTIME) | 注解在源码,class文件中存在且运行时可以通过反射机制获取到 |
来看一下@Override注解的保留策略:
@Retention(RetentionPolicy.SOURCE)
public @interface Override {
}
这表明@Override注解只在源码阶段存在,javac在编译过程中去去掉该注解.
@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) | 用于包 |
以@Override为例,不难看出其作用目标为方法:
@Target(ElementType.METHOD)
public @interface Override {
}
到现在,通过@interface,@Retention,@Target已经可以完整的定义一个注解,来看@Override完整定义:
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.SOURCE)
public @interface Override {
}
@Inherited
默认情况下,我们自定义的注解用在父类上不会被子类所继承.如果想让子类也继承父类的注解,即注解在子类也生效,需要在自定义注解时设置@Inherited.一般情况下该注解用的比较少.
@Documented
该注解用于描述其它类型的annotation应该被javadoc文档化,出现在api doc中.
比如使用该注解的@Target会出出现在api说明中.
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Target {
ElementType[] value();
}
借助@Interface,@Target,@Retention,@Inherited,@Documented这五个元注解,我们就可以自定义注解了,其中前三个注解是任何一个注解都必备具备的.
你以为下面会直接来将如何自定义注解嘛?不,你错了,我们还是来聊聊java自带的几个注解.
系统注解
java设计者已经为我们自定义了几个常用的注解,我们称之为系统注解,主要是这三个:
系统注解 | 含义 |
---|---|
@Override | 用于修饰方法,表示此方法重写了父类方法 |
@Deprecated | 用于修饰方法,表示此方法已经过时 |
@SuppressWarnnings | 该注解用于告诉编译器忽视某类编译警告 |
如果你已经完全知道这三者的用途,跳过这一小节,直接往下看.
@Override
它用作标注方法,说明被标注的方法重写了父类的方法,其功能类似断言.如果在一个没有重写父类方法的方法上使用该注解,java编译器将会以一个编译错误提示:
@Deprecated
当某个类型或者成员使用该注解时意味着
编译器不推荐开发者使用被标记的元素.另外,该注解具有”传递性”,子类中重写该注解标记的方法,尽管子类中的该方法未使用该注解,但编译器仍然报警.
public class SimpleCalculator {
@Deprecated
public int add(int x, int y) {
return x+y;
}
}
public class MultiplCalculator extends SimpleCalculator {
// 重写SimpleCalculator中方法,但不使用@Deprecated
public int add(int x, int y) {
return Math.abs(x)+Math.abs(y);
}
}
//test code
public class Main {
public static void main(String[] args) {
new SimpleCalculator().add(3, 4);
new MultiplCalculator().add(3,5);
}
}
对于像new SimpleCalculator().add(3,4)这种直接调用的,Idea会直接提示,而像第二种则不是直接提示:
但是在编译过程中,编译器都会警告:
需要注意@Deprecated和@deprecated这两者的区别,前者被javac识别和处理,而后者则是被javadoc工具识别和处理.因此当我们需要在源码标记某个方法已经过时应该使用@Deprecated,如果需要在文档中说明则使用@deprecated,因此可以这么:
public class SimpleCalculator {
/**
* @param x
* @param y
* @return
*
* @deprecated deprecated As of version 1.1,
* replace by <code>SimpleCalculator.add(double x,double y)</code>
*/
@Deprecated
public int add(int x, int y) {
return x+y;
}
public double add(double x,double y) {
return x+y;
}
}
@SuppressWarnning
该注解被用于有选择的关闭编译器对类,方法,成员变量即变量初始化的警告.该注解可接受以下参数:
参数 | 含义 |
---|---|
deprecated | 使用已过时类,方法,变量 |
unchecked | 执行了未检查的转告时的警告,如使用集合是为使用泛型来制定集合保存时的类型 |
fallthrough | 使用switch,但是没有break时 |
path | 类路径,源文件路径等有不存在的路径 |
serial | 可序列化的类上缺少serialVersionUID定义时的警告 |
finally | 任何finally字句不能正常完成时的警告 |
all | 以上所有情况的警告 |
滋溜一下,我们飞过了2016年,不,是看完了上一节.继