前言
注解(Annotation)这东西,大学学习的时候是从来没用过,然后学习框架的时候,发现注解是一个比一个多,而且说实话网上的教程一篇比一篇难懂,所以还是打算自己写一篇来填坑。
初识
比如说你现在要重写Object类的hashCode方法,那么你可以在你重写的方法上面加上一个@Override的注释,这样编译器就会帮你去检查这个方法父类有没有这个方法,方法签名符合不符合要求之类的,这个就是注解的作用。

我非常推荐到官方网站去直接学习。
注解,Annotation,本身属于元数据的一种。它提供了程序的有关数据,但是这些数据并不属于程序本身,对于注解标注的那些代码,并不起作用。这句话是不是有点颠覆三观,不对啊,我明明加了一个注解,程序就明明表现出不同的功能了,为什么会说注解不起作用呢?这个需要结合反射来说,因为本质上是反射来完成的,而不是注解。
总体来说,注解有三大作用:
- 为编译器提供信息:编译器可以读取注解,以此来发现错误。
- 编译的时候和部署的时候:一些工具可以根据注解的信息来生成代码等。
- 运行的时候:有一些注解可以在程序运行的时候进行检查。
基础
所有的注解都是以@开头的,这是为了告诉编译器,接下来的内容是注解。注解本身也可以有一些元素,比如这个注解就包含了两个元素:
1 |
如果注解仅有一个元素,且该元素的名字是value,那么就可以省略,直接写元素的值就可以了。如果注解没有元素,那么括号就可以省略,比如@Override和@Override()都是正确的。当然,多个注解可以同时在一起标注相同的元素。
哪里可以使用注解
在类上、方法上、域上或者是一些程序的元素上都可以有注解。除此之外,注解还可以出现在这些地方:
- 在创建对象的时候:
new @Interned MyObject(); - 类型转换:
myString = (@NonNull String) str; - 在implements的从句中:
class UnmodifiableList<T> implements @Readonly List<@Readonly T> { ... } - 异常抛出的地方:
void monitorTemperature() throws @Critical TemperatureException { ... }
声明注解类型
其实注解很多时候可以用来代替注释。比如经常有这类的代码:
1 | public class Generation3List extends Generation2List { |
上面的这段注释一目了然,但是还是有点不太美观,所以我们可以使用注解来进行优化,首先我们需要先声明一个注解,类似这样的:
1 | ClassPreamble { |
可以看到注解的定义和接口出奇的像,只不过在interface前面加了一个@,然后就是一些元素的说明,这些元素说明可以当成接口的方法,然后请注意,它们是可以有默认值的。
然后你就可以去使用这个注解了。比如我自己写了一个类,那么我可以使用这个注解。
1 |
|
因为有三个元素是有默认值的,所以可以不用提供。这样这个注解的定义和使用就完成了,是不是很简单呢?
预定义的注解
java自己是预先定义了一些注解,然后编译器可以使用这些注解,当然别的注解也可以使用这些预定义的注解。
java语言使用的注解类型
在java.lang包下,1.5定义了三个注解@Deprecated、@Override和@SuppressWarnings。1.7和1.8各新增了一个注解。
@Deprecated
被这个标注的元素,代表了已经过时,不应该继续使用。编译器检测到这个注解之后,会提醒用户;类似IDEA这种IDE还会给对应的方法等加上一条横线来表示这个方法已经被启用了。

除此之外,还应该在注释中,使用@deprecated 为什么被废弃这样来说明,注意!这里说的是注释里面,而且用的是小写。也就是正确的形式应该是下面这样的:
1 | /** |
@Override
这个标注就是告诉编译器,让它去检查是否是真的发生了重写。虽然不是必须的,但是非常非常推荐那么做。
@SuppressWarnings
对于某些警告,告知编译器不要输出,也就是忽视掉一些编译器给我们的警告信息。每一个编译器的警告信息都属于其中的一个类别,java语言有两种:deprecation和unchecked。在与泛型出现之前编写的旧代码进行交互时,可能会发生unchecked的警告。我们就可以使用这样的语法:@SuppressWarnings({"unchecked", "deprecation"})来压制这些警告。
@SafeVarargs
这个注解是1.7加入的,不太了解其作用,也没见过。
@FunctionalInterface
这个注解的意思是,该接口只有一个方法,这样就可以使用lambda表达式。
注解的注解(元注解)
除了java自己定义的5个注解外,还有5种注解是让别的注解所使用的注解,也即元注解。元注解在java.lang.annotation包中进行定义。
@Retention元注解标记了注解的存储方式。具体来说,有以下三种:SOURCE:直接被编译器丢弃,也即只有在源码中才能看到注解CLASS:不会被编译器丢弃,即能够在class文件中找到,但是会被vm丢弃,默认就是这种。RUNTIME:VM也不会丢弃,这样就可以通过反射来获取。基本上你在框架中看到的所有注解都是这个类型的。
@Documented:默认情况下,Javadoc下并不会包含注解的信息,但是加了这个之后就可以有。@Target:确定对应的注解可以出现在哪些位置,具体来说有以下这些:ElementType.ANNOTATION_TYPE能够标注在注解上ElementType.CONSTRUCTOR构造器ElementType.FIELD属性ElementType.LOCAL_VARIABLE局部变量ElementType.METHOD方法ElementType.PACKAGE包ElementType.PARAMETER方法的参数ElementType.TYPE任何地方
@Inherited:可以从父类中继承。@Repeatable:在jdk8中新引入的,表示注解可以重复。
类型注解和可插入注解系统
在jdk8之前,注解基本只能出现在一些常见的地方,但是到了JDK8,注解几乎可以出现在任何地方了,比如上面可以在类型转换中使用,我们称之为类型注解Type annotation。有了类型注解,我们就可以使用更强的类型检查了(JDK本身并没有提供,但是我们可以自己写或者下载)。比如我希望某个变量不能是Null,这样就不会触发NPE,所以就可以这样写,@NonNull String str;,帮助你写出更为强大的代码。
总结
注解本身是非常非常简单的,造成困惑的本身,是因为框架会使用注解,再加上背后的反射机制来完成复杂的功能,而正是因为反射和注解同时出现,才造成了这个困惑。其实只需要记住一句话,就很容易理解注解了:
Annotations have no direct effect on the operation of the code they annotate.