Annotation 中文译过来就是注解、标释的意思,在 Java 中注解是一个很重要的知识点,但经常还是有点让新手不容易理解。
我们经常可以看到一些文章对注解的解释如下:
Java 注解用于为 Java 代码提供元数据。作为元数据,注解不直接影响你的代码执行,但也有一些类型的注解实际上可以用于这一目的。Java 注解是从 Java5 开始添加到 Java 的。
这段对于注解(Annotation)的解释确实正确,但是说实在话,我第一次学习的时候,头脑一片空白。这什么跟什么啊?听了像没有听一样。因为概念太过于抽象,所以初学者实在是比较吃力才能够理解,然后随着自己开发过程中不断地强化练习,才会慢慢对它形成正确的认识。
所以,我理解的注解就是一种标签,想像代码具有生命,注解就是对于代码中某些鲜活个体的贴上去的一张标签,那么注解就是给编译器看的一种标签,通过这个标签,编译器可以快速“联想”到具体的代码。
一、注解的语法
熟悉Java Web开发的同学对注解应该不会陌生,Spring家族使用了大量的注解来简化了我们的开发,其实同和class和 interface 一样,注解也属于一种类型。它是在 Java SE 5.0 版本中开始引入的概念。
1、注解的定义
注解通过@interface
注解来定义
public @interface MyAnnotation{
//TODO
}
它的形式跟接口很类似,不过前面多了一个 @
符号。上面的代码就创建了一个名字为 MyAnnotation的注解。
你可以简单理解为创建了一张名字为 MyAnnotation的标签。
定义好注解之后,我们就可以使用注解了,最简单的就是在一个类头上使用注解:
@MyAnnotation
public class App{
//TODO
}
你可以简单理解为将 MyAnnotation这张标签贴到 Test 这个类上面。不过,要想注解能够正常工作,还需要介绍一下一个新的概念那就是元注解。
2、元注解
元注解是什么意思呢?
元注解是可以加解到注解上的注解,或者说元注解是一种基本注解,但是它能够应用到其它的注解上面。
如果难于理解的话,你可以这样理解。元注解也是一张标签,但是它是一张特殊的标签,它的作用和目的就是给其他普通的标签进行解释说明的。
Java提供的元标签有 @Retention、@Documented、@Target、@Inherited、@Repeatable 5 种。下面详细来讲解一下。
(1)@Rentention
Retention 的英文意为保留期的意思。当 @Retention 应用到一个注解上的时候,它解释说明了这个注解的的存活时间。
它的取值如下:
- RetentionPolicy.SOURCE 注解只在源码阶段保留,在编译器进行编译时它将被丢弃忽
- RetentionPolicy.CLASS 注解只被保留到编译进行的时候,它并不会被加载到 JVM 中
- RetentionPolicy.RUNTIME 注解可以保留到程序运行的时候,它会被加载进入到 JVM 中,所以在程序运行时可以获取到它们。
注意:SOURCE和CLASS级别需要继承AbstractProcessor并实现process方法来寻找并处理自定义注解,RUNTIME是我们日常使用最多的,无需继承AbstractProcessor,配合Java反射机制就可以处理自定义注解。
当使用@Rentention标注在一个注解上的时候就表示指定这个注解的工作时间。
@Rentention(RententionPolicy.RUNTIME)
public @interface MyAnnotation{
//TODO
}
上面的代码中,我们指定 MyAnnotation可以在程序运行周期被获取到,因此它的生命周期非常的长。
(2)@Document
这是一个和文档有关的注解,它的作用是能够将注解中的元素包含到 Javadoc 中去。
(3)@Target
Target 是目标的意思,@Target 指定了注解运用的地方。当一个注解被 @Target 注解时,这个注解就被限定了运用的场景。@Target 有下面的取值:
- ElementType.ANNOTATION_TYPE:可以在注解上使用
- ElementType.CONSTRUCTOR:可以在构造器上使用
- ElementType.FIELD:可以在属性上使用
- ElementType.LOCAL_VARIABLE:可以在局部变量上使用
- ElementType.METHOD:可以在方法上使用
- ElementType.PACKAGE:可以在包名上使用
- ElementType.PARAMETER:可以在一个方法的形参上使用
- ElementType.TYPE:可以在类、接口、枚举类等头上使用
(4)@Inherited
Inherited 是继承的意思,但是它并不是说注解本身可以继承,而是说如果一个超类被 @Inherited 注解过的注解进行注解的话,那么如果它的子类没有被任何注解应用的话,那么这个子类就继承了超类的注解。
@Inherited
@Target({ElementType.TYPE,ElementType.METHOD,ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
public @interface MyAnnotation {
//TODO
}
---------------------------------------------------------
@MyAnnotation
public class SuperClass{
//TODO
}
public class SubClass extends SuperClass{
//TODO
}
注解 MyAnnotation被 @Inherited 修饰,之后类 SuperClass被 MyAnnotation注解,类 SubClass 继承 SuperClass,虽然没有在类SubClass上标注,但是类 SubClass 也拥有 MyAnnotation这个注解。这是@Inherited注解的含义。
(5)@Repeatable
Repeatable 自然是可重复的意思。@Repeatable 是 Java 1.8 才加进来的,所以算是一个新的特性。他的含义是说明这个注解可以在同一个Element上多次标记。
public @interface Persons {
Person[] value();
}
@Repeatable(Persons.class)
public @interface Person {
String role() default "";
}
--------------------------------------------------------------
@Person(role="artist")
@Person(role="coder")
@Person(role="PM")
public class SuperMan{
}
在Person注解上使用@Repeatable之后,@Person就可以在一个类上多次使用。
好了,关于Java中的5个元注解就说到这,接下来,我们在来了解一下注解的属性如果定义。
3、注解的属性
注解的属性也叫做成员变量。注解只有成员变量,没有方法。注解的成员变量在注解的定义中以“无形参的方法”形式来声明,其方法名定义了该成员变量的名字,其返回值定义了该成员变量的类型。比如:
@Inherited
@Target({ElementType.TYPE,ElementType.METHOD,ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
public @interface MyAnnotation {
String value();
int id();
}
上面就定义了给MyAnnotation这个注解定义了两个属性:value和key。在定义的时候我们还可以使用default
关键字给属性指定默认值:
@Inherited
@Target({ElementType.TYPE,ElementType.METHOD,ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
public @interface MyAnnotation {
String value() default "";
int id() default 0;
}
上面给两个属性有指定了默认值,在使用的时候我们也可以给属性指定一个值,多个属性用逗号(,
)隔开:
@MyAnnotation(value="Java",id=1)
public class TestClass{
//TODO
}
另外在使用注解的时候,如果注解中只有一个属性,那么可以不写属性名直接写值也可以,如果注解中没有任何属性,那么直接使用注解标注就可以了。
二、Java中预置的重要注解
学习了上面相关的知识,我们已经可以自己定义一个注解了。其实 Java 语言本身已经提供了几个现成的注解。
(1)@Deprecated
这个是用来标记过期元素的,他几乎何以作用在任何元素上。如果在我们的代码中使用额被@Deprecated标记的API,那么在编译阶段编译器就会发出警告,告诉开发者正在调用一个过时的元素比如过时的方法、过时的类、过时的成员变量。
(2)@Override
这个大家应该很熟悉了,提示子类要复写父类中被 @Override 修饰的方法
(3)@SuppressWarnings
阻止警告的意思。之前说过调用被 @Deprecated 注解的方法后,编译器会警告提醒,而有时候开发者会忽略这种警告,他们可以在调用的地方通过 @SuppressWarnings 达到目的。
@Person(role="artist")
@Person(role="coder")
@Person(role="PM")
public class SuperMan {
public void coding(){
}
@SuppressWarnings(value = "deprecation")
@Deprecated
public void speak(){
}
public void drawing(){
}
}
(4)@SafeVarargs
参数安全类型注解。它的目的是提醒开发者不要用参数做一些不安全的操作,它的存在会阻止编译器产生 unchecked 这样的警告。它是在 Java 1.7 的版本中加入的。
(5)@FunctionalInterface
函数式接口注解,这个是 Java 1.8 版本引入的新特性。函数式编程很火,所以 Java 8 也及时添加了这个特性。
函数式接口 (Functional Interface) 就是一个只有一个抽象方法的接口(注意是只有一个抽象方法,不是只有一个方法)。比如:
@FunctionalInterface
public interface Runnable {
/**
* When an object implementing interface <code>Runnable</code> is used
* to create a thread, starting the thread causes the object's
* <code>run</code> method to be called in that separately executing
* thread.
* <p>
* The general contract of the method <code>run</code> is that it may
* take any action whatsoever.
*
* @see java.lang.Thread#run()
*/
public abstract void run();
}
我们进行线程开发中常用的 Runnable 就是一个典型的函数式接口,上面源码可以看到它就被 @FunctionalInterface 注解。
三、注解的提取
前面我通过用标签来比作注解,前面的内容是讲怎么写注解,然后贴到哪个地方去,而现在我们要做的工作就是检阅这些标签内容。 形象的比喻就是你把这些注解标签在合适的时候取下来,然后检阅上面的内容信息。
要想正确检阅注解,离不开一个手段,那就是反射。
注解与反射
注解通过反射获取。可以通过 Class 类的 isAnnotationPresent() 方法判断是否应用了某个注解,使用isAnnotation()判断是否是注解
public boolean isAnnotationPresent(Class<? extends Annotation> annotationClass) {}
public boolean isAnnotation(){}
然后可以使用 Class 类的getAnnotation() 方法或getAnnotations() 方法来获取 Annotation 对象。
public <A extends Annotation> A getAnnotation(Class<A> annotationClass) {}
public Annotation[] getAnnotations() {}
前一个方法返回指定类型的注解,后一个方法返回注解到这个元素上的所有注解。
如果获取到的 Annotation 如果不为 null,则就可以调用它们的属性方法了。比如
@Inherited
@Target({ElementType.TYPE, ElementType.METHOD, ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
public @interface MyAnnotation {
String name() default "";
int age() default 0;
}
-----------------------------------------------------------------------------
@MyAnnotation(name = "haungxin",age=20)
public class App {
public static void main(String[] args) {
//判断MyAnnotation注解是否在App这个类上出现
if(App.class.isAnnotationPresent(MyAnnotation.class)){
//获取App类上的注解
MyAnnotation myAnnotation = App.class.getAnnotation(MyAnnotation.class);
//获取注解的值
System.out.println("name="+myAnnotation.name());
System.out.println("age="+myAnnotation.age());
}
}
}
执行结果:
四、注解的使用场景
我相信讲到这里大家都很熟悉了注解,但是有不少同学肯定会问,注解到底有什么用呢?
对啊注解到底有什么用?
我们不妨将目光放到 Java 官方文档上来。
文章开始的时候,我用标签来类比注解。但标签比喻只是我的手段,而不是目的。为的是让大家在初次学习注解时能够不被那些抽象的新概念搞懵。既然现在,我们已经对注解有所了解,我们不妨再仔细阅读官方最严谨的文档。
注解是一系列元数据,它提供数据用来解释程序代码,但是注解并非是所解释的代码本身的一部分。注解对于代码的运行效果没有直接影响。
注解有许多用处,主要如下:
- 提供信息给编译器: 编译器可以利用注解来探测错误和警告信息
- 编译阶段时的处理: 软件工具可以用来利用注解信息来生成代码、Html文档或者做其它相应处理。
- 运行时的处理: 某些注解可以在程序运行的时候接受代码的提取
值得注意的是,注解不是代码本身的一部分。注解主要针对的是编译器和其它工具软件(SoftWare tool)。
当开发者使用了Annotation 修饰了类、方法、Field 等成员之后,这些 Annotation 不会自己生效,必须由开发者提供相应的代码来提取并处理 Annotation 信息。这些处理提取和处理 Annotation 的代码统称为 APT(Annotation Processing Tool)。 所以现在,我们可以给自己答案了,注解有什么用?给谁用?给编译器或者 APT 用的。
因此,注解能干什么,取决于你想用它来干什么!
总结
- 如果注解难于理解,你就把它类同于标签,标签为了解释事物,注解为了解释代码。
- 注解的基本语法,创建如同接口,但是多了个 @ 符号。
- 注解的元注解。
- 注解的属性。
- 注解主要给编译器及工具类型的软件用的。
- 注解的提取需要借助于 Java 的反射技术,反射比较慢,所以注解使用时也需要谨慎计较时间成本。
参考:
https://blog.csdn.net/briblue/article/details/73824058