前言
官方定义:
Java 注解用于为 Java 代码提供元数据。作为元数据,注解不直接影响你的代码执行,但也有一些类型的注解实际上可以用于这一目的。Java 注解是从 Java5 开始添加到 Java 的。
这也太官话了,听不懂。。。假设代码有生命的话,注解就是代码的标签。举个例子,人也会被打标签,比如帅哥就是我的标签。注解就是代码的标签。
既然注解是标签,如何给代码打标签呢,别急,让我们先从注解的注解来讲起,即java的元注解。
注解的注解,翻译一下就是注解的标签。
元注解
元注解有 @Retention、@Target、@Inherited、@Repeatable、 @Documented5 种。
@Retention
当 @Retention 应用到一个注解上的时候,它解释说明了这个注解的的存活时间。
① RetentionPolicy.SOURCE 注解只在源码阶段保留,在编译器进行编译时它将被丢弃忽视。
② RetentionPolicy.CLASS 注解只被保留到编译进行的时候,它并不会被加载到 JVM 中。
③ RetentionPolicy.RUNTIME 注解可以保留到程序运行的时候,它会被加载进入到 JVM 中,所以在程序运行时可以获取到它们。
再浅显点,第一种就是java文件的时候有效,第二种就是java文件编译成class文件时一直有效,第三种就是class文件加入到虚拟机时,一直有效。
//活得最久的注解
@Retention(RetentionPolicy.RUNTIME)
public @interface Test {
}
@Target
这个标签指定了注解运用的地方,当一个注解被@Target注解时,这个注解就被限定了运用的场景。
ElementType.ANNOTATION_TYPE 可以给一个注解进行注解
ElementType.CONSTRUCTOR 可以给构造方法进行注解
ElementType.FIELD 可以给属性进行注解
ElementType.LOCAL_VARIABLE 可以给局部变量进行注解
ElementType.METHOD 可以给方法进行注解
ElementType.PACKAGE 可以给一个包进行注解
ElementType.PARAMETER 可以给一个方法内的参数进行注解
ElementType.TYPE 可以给一个类型进行注解,比如类、接口、枚举
//只能用在方法上
@Target(ElementType.METHOD)
//活得最久的注解
@Retention(RetentionPolicy.RUNTIME)
public @interface Test {
}
@Inherited
一个超类被 @Inherited 注解过的注解进行注解的话,那么如果它的子类没有被任何注解应用的话,那么这个子类就继承了超类的注解。
举个例子,父亲被打上了富豪的标签,那他儿子就是富二代。这就是@Inherited的作用。
@Repeatable
@Repeatable 是 Java 1.8 才加进来的,所以算是一个新的特性。什么样的注解会多次应用呢?通常是注解的值可以同时取多个。比如一个男人既是丈夫又是孩子,有多个标签。
@Documented
它的作用是能够将注解中的元素包含到 Javadoc 中去。
注解的属性
注解的属性也叫做成员变量。注解只有成员变量,没有方法。注解的成员变量在注解的定义中以“无形参的方法”形式来声明,其方法名定义了该成员变量的名字,其返回值定义了该成员变量的类型。
//只能用在方法上
@Target(ElementType.METHOD)
//活得最久的注解
@Retention(RetentionPolicy.RUNTIME)
public @interface Test {
String name();
int age();
}
测试类:
public class TestAnnotation {
@Test(name = "小沈阳",age = 30)
void test(){
}
}
需要注意的是,在注解中定义属性时它的类型必须是 8 种基本数据类型外加 类、接口、注解及它们的数组。
注解的提取
我给代码打了标签,怎么去获取它,获取了之后又要拿来做什么?
注解通过反射获取。
public class TestAnnotation {
@Test(name = "小沈阳",age = 30)
public void todo(){
try {
Method method = TestAnnotation.class.getMethod("todo");
//是否应用了注解
if (method.isAnnotationPresent(Test.class)) {
Test annotation = method.getAnnotation(Test.class);
System.out.println("name: " + annotation.name());
System.out.println("age: " + annotation.age());
}
} catch (Exception e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
TestAnnotation annotation= new TestAnnotation();
annotation.todo();
}
}
首先通过反射获取method,在判断在这个方法种是否用到了注解,用到了之后再去获取注解的值。
运行结果:
name: 小沈阳
age: 30
上面只是举了个作用在方法上面的注解提取。类,属性都可以提取注解,不过还是得借助于反射。
注解的使用场景
再次回到官方文档上来:
注解是一系列元数据,它提供数据用来解释程序代码,但是注解并非是所解释的代码本身的一部分。注解对于代码的运行效果没有直接影响。
注解有许多用处,主要如下:
提供信息给编译器: 编译器可以利用注解来探测错误和警告信息
编译阶段时的处理: 软件工具可以用来利用注解信息来生成代码、
Html文档或者做其它相应处理。
运行时的处理: 某些注解可以在程序运行的时候接受代码的提取
值得注意的是,注解不是代码本身的一部分。
当开发者使用了Annotation 修饰了类、方法、Field 等成员之后,这些 Annotation 不会自己生效,必须由开发者提供相应的代码来提取并处理 Annotation 信息。这些处理提取和处理 Annotation 的代码统称为 APT(Annotation Processing Tool)。
所以注解是给编译器用的或者APT用的,比如ButterKnife,再比如Retrofit的注解,具体的实现就不具体说了,感兴趣可以去看看开源框架的源码。
关于java预置的注解
@Deprecated
这个元素是用来标记过时的元素。编译器在编译阶段遇到这个注解时会发出提醒警告,告诉开发者正在调用一个过时的元素比如过时的方法、过时的类、过时的成员变量。
@Documented
//活得最久
@Retention(RetentionPolicy.RUNTIME)
//作用范围贼广
@Target(value={CONSTRUCTOR, FIELD, LOCAL_VARIABLE, METHOD, PACKAGE, PARAMETER, TYPE})
public @interface Deprecated {
}
看到这些是不是貌似都很熟悉呢。
@Override
子类要复写父类中被 @Override 修饰的方法
//作用范围在方法中
@Target(ElementType.METHOD)
//只在java文件中有效,命短
@Retention(RetentionPolicy.SOURCE)
public @interface Override {
}
@SuppressWarnings
阻止警告的意思。之前说过调用被 @Deprecated 注解的方法后,编译器会警告提醒,而有时候开发者会忽略这种警告,他们可以在调用的地方通过 @SuppressWarnings 达到目的。
@Target({TYPE, FIELD, METHOD, PARAMETER, CONSTRUCTOR, LOCAL_VARIABLE})
@Retention(RetentionPolicy.SOURCE)
public @interface SuppressWarnings {
String[] value();
}
总结
注解的提取需要借助java的反射技术,反射比较耗资源,而且会跳过安全检查。Apt技术要慎用。。
网友评论