【Java 注解】笔记整理
阅读文章:
1、秒懂,Java 注解 (Annotation)你可以这样学
2、深入浅出Java注解
3、Java 技术之注解 Annotation
4、AbstractProcessor注解处理器
注:下文 1 - 3 主要摘抄自 深入浅出Java注解
1、注解的定义
Annotation(注解)就是Java提供了一种源程序中的元素关联任何信息或者任何元数据(metadata)的途径和方法。从Java 1.5开始支持支持。(元数据是描述数据的数据)
Annotation是被动的元数据,永远不会有主动行为。
特别说明:
- 注解仅仅是元数据,和业务逻辑无关,所以当你查看注解类时,发现里面没有任何逻辑处理;
- javadoc中的@author、@version、@param、@return、@deprecated、@hide、@throws、@exception、@see是标记,并不是注解;
2、注解的作用
提供信息给编译器: 编译器可以利用注解来探测错误和警告信息;
如格式检查:告诉编译器信息,比如被@Override
标记的方法如果不是父类的某个方法,IDE会报错编译阶段时的处理: 软件工具可以利用注解信息来生成代码、Html文档或者做其它相应处理;
如减少配置:运行时动态处理,得到注解信息,实现代替配置文件的功能;运行时的处理: 某些注解可以在程序运行的时候接受代码的提取。
如减少重复工作:比如第三方框架xUtils,通过注解@ViewInject
减少对 findViewById 的调用,类似的还有(JUnit、ActiveAndroid等);
3、自定义注解
特征:
- 注解类会被
@interface
标记; - 注解类的顶部会被
@Documented
、@Retention
、@Target
、@Inherited
这四个注解标记(@Documented、@Inherited
可选,@Retention、@Target
必须要有); - 在 Java 1.8 新加了
@Repeatable
(1)@Target:
作用:用于描述注解的使用范围,即被描述的注解可以用在什么地方。
取值:
1、CONSTRUCTOR:构造器;
2、FIELD:实例;
3、LOCAL_VARIABLE:局部变量;
4、METHOD:方法;
5、PACKAGE:包;
6、PARAMETER:参数;
7、TYPE:类、接口(包括注解类型) 或enum声明。
源码:
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Target {
/**
* Returns an array of the kinds of elements an annotation type
* can be applied to.
* @return an array of the kinds of elements an annotation type
* can be applied to
*/
ElementType[] value();
}
从源码中可以看到,value
为 ElementType
数组类型,而 ElementType
实际上是一个枚举,因此 @Target
的取值可以取单个或者多个,只要是在一个数组内即可。例如:
@Target({ElementType.METHOD,ElementType.FIELD})
另外,如果注解中只有一个元素时,一般默认命名为 value
,且在赋值的时候可以省略名字。下面为完整的书写:
@Target(value = {ElementType.METHOD,ElementType.FIELD})
(2)@Retention:
作用:表示需要在什么级别保存该注解信息,用于描述注解的生命周期,即被描述的注解在什么范围内有效;
取值:
1、SOURCE:在源文件中有效,即源文件(.java)保留,在编译器进行编译时它将被丢弃忽视(编译之后生成 .class是不会保留的)
2、CLASS:在class文件中有效,注解只被保留到编译进行的时候,即class保留,它并不会被加载到 JVM 中
3、RUNTIME:在运行时有效,即运行时保留,会被加载进入到 JVM 中,所以在程序运行时可以获取到它们。
(3)@Documented:
作用:用于描述其它类型的annotation应该被作为被标注的程序成员的公共API,因此可以被例如 javadoc 此类的工具文档化。
取值:它属于标记注解,没有成员;
(4)@Inherited:
作用:用于描述某个被标注的类型是可被继承的。如果一个使用了 @Inherited
修饰的 annotation 类型被用于一个 class,则这个 annotation 将被用于该 class 的子类。
取值:它属于标记注解,没有成员;
(5)@Repeatable:
详见:秒懂,Java 注解 (Annotation)你可以这样学
自定义注解格式:
元注解
public @interface 注解名{
定义体;
}
注解参数可支持的数据类型:
1、所有基本数据类型(int,float,boolean,byte,double,char,long,short);
2、String类型;
3、Class类型;
4、enum类型;
5、Annotation类型;
6、以上所有类型的数组。
特别说明:
1、注解类中的方法只能用public或者默认这两个访问权修饰,不写public就是默认
2、如果注解类中只有一个成员,最好把方法名设置为”value”,因为一个注解内仅仅只有一个名字为 value 的属性时,应用这个注解时可以直接接属性值填写到括号内。
3、注解元素必须有确定的值,要么在定义注解的默认值中指定,要么在使用注解时指定,非基本类型的注解元素的值不可为null。因此, 使用空字符串或0作为默认值是一种常用的做法。如果没有指定默认值,而且在使用该注解的时候也没有指定值,那么编译器会报错,并且不能通过编译。
@Target(value = {ElementType.METHOD,ElementType.FIELD})
public @interface AnnotationTest {
String author();
Priority priority() default Priority.MEDIUM;//枚举类型
Status status() default Status.NOT_STARTED;//枚举类型
}
enum Priority {LOW, MEDIUM, HIGH}
enum Status {STARTED, NOT_STARTED}
4、注解的解析
在没看那两篇文章的时候,虽然大概知道注解的作用,但是会有一个疑惑,我使用了这个注解,但是它是怎么起作用的呢?明明在定义这个注解的时候我就定义了其中的成员元素,其余的啥都没做了呀!
对于注解的解析,会根据 Retention 的不同而有不同的解析过程。
(1) 对于 @Retention(RetentionPolicy.CLASS)
对于 RetentionPolicy.CLASS
的注解,具体的实现就比较复杂了,除了要实现一个继承自 AbstractProcessor 的自定义 Processor 类,因为在编译的时候会扫描全部继承自 AbstractProcessor 的自定义类,执行里面的 process
方法。
另外,还要额外的设置一些东西,大致如下:
1. 在 main 目录下新建 resources 资源文件夹;
2. 在 resources文件夹下建立 META-INF/services 目录文件夹;
3. 在 META-INF/services 目录文件夹下创建 javax.annotation.processing.Processor 文件;
4. 在 javax.annotation.processing.Processor 文件写入注解处理器的全称(即自定义的 Processor 类的全称 ),包括包路径;
当然上面这四步也可以用谷歌的一个库来自动实现,首先需要导入
'com.google.auto.service:auto-service:1.0-rc4'
然后在自定义的 Processor 类前用 @AutoService(Processor.class)
修饰。
下面,我还是来说一下在 Android Studio 里面实现的步骤。
假设我要定义一个注解 @AnnotationTest
,具体实现如下:
@Retention(RetentionPolicy.CLASS)
@Target(ElementType.METHOD)
public @interface AnnotationTest {
}
本身没有啥意义,就是用来演示用的。
然后需要定义对应的注解处理器 AnnotationTestProcessor
,源码在下面贴出来,先看下去。
一般来说,注解处理器只是在编译的时候用到,正式的时候并不需要打包到 apk 中,所以会分别建两个 Java Library,名字自拟,一个放自定义的注解,另一个放注解处理器。
注意:在 AS 中新建 Java Library 的步骤就是 "File -> New -> New Module..."
,然后在弹出的面板里面选择 Java Library
就可以了。
这里 processor-lib
放自定义的注解:
然后 processor
放对应的注解处理器:
且在其 build.gradle 文件里面有如下依赖:
//前面说过的第三方库
implementation 'com.google.auto.service:auto-service:1.0-rc4'
//依赖含有 AnnotationTest 注解的 library
implementation project(':processor-lib')
//一个第三方库,用于生成 Java 文件的
implementation 'com.squareup:javapoet:1.10.0'
这里自定义的注解处理器,为了演示,在其 process
方法里面实现生成一个 HelloWorld
的 java 文件,所以有在上面依赖 javapoet
。具体代码如下:
//使用 auto-service
@AutoService(Processor.class)
public class AnnotationTestProcessor extends AbstractProcessor {
private Filer filer;
@Override
public synchronized void init(ProcessingEnvironment processingEnv) {
super.init(processingEnv);
filer = processingEnv.getFiler();
}
@Override
public Set<String> getSupportedAnnotationTypes() {
Set<String> set = new LinkedHashSet<>();
set.add(AnnotationTest.class.getCanonicalName());
return set;
}
@Override
public SourceVersion getSupportedSourceVersion() {
return SourceVersion.latestSupported();
}
@Override
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
// 创建test方法
MethodSpec test = MethodSpec.methodBuilder("test")//
.addModifiers(Modifier.PUBLIC, Modifier.STATIC)//
.returns(void.class)//
.addStatement("$T.out.println($S)", System.class, "自动创建的")//
.build();
// 创建HelloWorld类
TypeSpec helloWorld = TypeSpec.classBuilder("HelloWorld")//
.addModifiers(Modifier.PUBLIC)//
.addMethod(test)//
.build();
String packageName = processingEnv.getElementUtils().getPackageOf(annotatedElement).getQualifiedName().toString();
try {
JavaFile javaFile = JavaFile.builder(packageName, helloWorld)//
.addFileComment(" This codes are generated automatically. Do not modify!")//
.build();
javaFile.writeTo(filer);
} catch (IOException e) {
e.printStackTrace();
}
}
return false;
}
}
对于实现的各个方法,不讲其具体含义,谷歌一大把。
最后就是使用了,如果是在一个 module
里面使用,则需要添加如下依赖:
implementation project(':processor-lib')
annotationProcessor project(':processor')
其中,因为 processor
library 只需要在编译的时候用到,所以需要使用到 annotationProcessor
(用来替代 android-apt
的,具体看这里)。
然后就是具体的使用了,假设我在 MainActivity
方法里面定义一个方法,并使用 AnnotationTest
注解修饰,如下:
@AnnotationTest
public void test() {
Log.d("TAG", "This is a test");
}
此时,build 一下该 module,就可以看到有生成在自定义注解处理器中定义的 HelloWorld.java
:
注意,如果在 module 里面没有使用过 AnnotationTest
注解(而 test()
方法不一定需要被调用),是不会生成 HelloWorld.java
的。在生成了 HelloWorld.java
之后,就可以直接使用了。如下:
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
test();
}
@AnnotationTest
public void test() {
Log.d("TAG", "This is a test");
//直接使用
HelloWorld.test();
}
}
(2) 对于 @Retention(RetentionPolicy.SOURCE)
虽然看似在使用 RetentionPolicy.SOURCE
类型注解的时候,是 Java集成开发环境(IDE)工具软件在进行提示(本人用的是 IntelliJ IDEA ),如:
而如果直接使非 IDE,如文本编辑器等,在敲上述代码的时候,是不会想上面那样报错的,但是当你使用命令行去编译 .java
文件的时候,也会报错(同时是上面那段代码):
这就表明,当注解类型为 RetentionPolicy.SOURCE
时,就像前面说的,本质上是作用于 .java
文件编译成 .class
文件阶段的( javac
是收录于 JDK 中的 Java 语言编译器),IDE 能够在你敲代码的时候就进行提示,只不过是通过技术手段(不深究)实现了提前提示,而不用等到去编译 .java
文件的时候再提示,因为 IDE 在编译 .java
的时候本质上也是借助 javac
。
但是在编译成 .class
文件后,@Override
就会被抛弃,而不会保留到 .class
文件中。但是在编译的时候,也能像 RetentionPolicy.CLASS
那样被自定义注解处理器扫描到,并作出相应的逻辑。
这里一定要注意,只是注解不会被保留到 .class
的文件中,但是注解起到的作用,还是可以注解自定义注解处理器实现的。
因为我把 (1) 中的 RetentionPolicy.CLASS
改成的 RetentionPolicy.SOURCE
也同样能生成 HelloWorld.java
。
(3) 对于 @Retention(RetentionPolicy.RUNTIME)
对于 RetentionPolicy.RUNTIME
的注解,会在运行时保留,被加载进入到 JVM 中,因此在程序运行时可以获取到,主要是利用 反射机制
在代码运行过程中动态地执行一些操作,具体的示例可以参考 Java 技术之注解 Annotation。
当然也可以像 (2) 一样使用注解处理器
。
但是就性能上而言,反射比注解处理器差点,因为注解处理器是在编译的时候就处理好了,但是使用反射会方便一点,不需要定义额外的注解处理器。
上一篇: KMP算法之next数组求解(代码)
下一篇: 线程池execute与submit的区别