0%

Java-Annotation补坑

前言

注解(Annotation)这东西,大学学习的时候是从来没用过,然后学习框架的时候,发现注解是一个比一个多,而且说实话网上的教程一篇比一篇难懂,所以还是打算自己写一篇来填坑。

初识

比如说你现在要重写Object类的hashCode方法,那么你可以在你重写的方法上面加上一个@Override的注释,这样编译器就会帮你去检查这个方法父类有没有这个方法,方法签名符合不符合要求之类的,这个就是注解的作用。

1577085694863

我非常推荐到官方网站去直接学习。

注解,Annotation,本身属于元数据的一种。它提供了程序的有关数据,但是这些数据并不属于程序本身,对于注解标注的那些代码,并不起作用。这句话是不是有点颠覆三观,不对啊,我明明加了一个注解,程序就明明表现出不同的功能了,为什么会说注解不起作用呢?这个需要结合反射来说,因为本质上是反射来完成的,而不是注解。

总体来说,注解有三大作用:

  • 为编译器提供信息:编译器可以读取注解,以此来发现错误。
  • 编译的时候和部署的时候:一些工具可以根据注解的信息来生成代码等。
  • 运行的时候:有一些注解可以在程序运行的时候进行检查。

基础

所有的注解都是以@开头的,这是为了告诉编译器,接下来的内容是注解。注解本身也可以有一些元素,比如这个注解就包含了两个元素:

1
2
3
4
@User(
name = "clp",
age = "33"
)

如果注解仅有一个元素,且该元素的名字是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
2
3
4
5
6
7
8
9
10
11
12
public class Generation3List extends Generation2List {

// Author: John Doe
// Date: 3/17/2002
// Current revision: 6
// Last modified: 4/12/2004
// By: Jane Doe
// Reviewers: Alice, Bill, Cindy

// class code goes here

}

上面的这段注释一目了然,但是还是有点不太美观,所以我们可以使用注解来进行优化,首先我们需要先声明一个注解,类似这样的:

1
2
3
4
5
6
7
8
9
@interface ClassPreamble {
String author();
String date();
int currentRevision() default 1;
String lastModified() default "N/A";
String lastModifiedBy() default "N/A";
// Note use of array
String[] reviewers();
}

可以看到注解的定义和接口出奇的像,只不过在interface前面加了一个@,然后就是一些元素的说明,这些元素说明可以当成接口的方法,然后请注意,它们是可以有默认值的。

然后你就可以去使用这个注解了。比如我自己写了一个类,那么我可以使用这个注解。

1
2
3
4
5
6
7
8
@ClassPreamble(
author = "clp",
date ="2020",
reviewers = {"aa","bb","cc"}
)
public class Annotation {
// some code here
}

因为有三个元素是有默认值的,所以可以不用提供。这样这个注解的定义和使用就完成了,是不是很简单呢?

预定义的注解

java自己是预先定义了一些注解,然后编译器可以使用这些注解,当然别的注解也可以使用这些预定义的注解。

java语言使用的注解类型

java.lang包下,1.5定义了三个注解@Deprecated@Override@SuppressWarnings。1.7和1.8各新增了一个注解。

@Deprecated

被这个标注的元素,代表了已经过时,不应该继续使用。编译器检测到这个注解之后,会提醒用户;类似IDEA这种IDE还会给对应的方法等加上一条横线来表示这个方法已经被启用了。

deprecated

除此之外,还应该在注释中,使用@deprecated 为什么被废弃这样来说明,注意!这里说的是注释里面,而且用的是小写。也就是正确的形式应该是下面这样的:

1
2
3
4
5
6
7
/**
* @deprecated 这个方法为什么会被废弃
*/
@Deprecated
public void badMethod() {
// some code
}

@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.