【Java高级特性】注解

  • java在JDK1.5以后引入了注解(Annotation)的功能。
  • 注解主要用于说明代码的,是代码的特殊标记,因此是编译器和JVM可见的,而通常的注释是被编译器和JVM完全忽略的,这就是注解和注释的最主要区别。
  • 注解主要有三个方面的作用:分别是编写文档、编译检查、代码分析
  • JavaSE中,注解作用有限,但在JavaEE中,作用很大,可以代替XML配置

一、常用注解

1. 用于文档生成的注解

注解 作用 格式要求 备注
@author 标明开发该类的作者 @author 姓名/组织/邮箱 多个作者之间用逗号隔开
@version 标明该类的版本 @version 当前版本号 ————————
@see 标明相关联的类 @see 类名/方法名 可用于类和方法
@since 从哪个版本开始增加 @since 创建日期/初始版本号 ————————
@param 对方法参数的说明 @param 形参名 形参类型 形参说明 1. 仅用于方法;2. 没有参数则不能写;3. 可并列多个
@return 对方法返回值的说明 @return 返回值类型 返回值说明 1. 仅用于方法;2.返回值是void则不能写
@exception 对方法可能抛出的异常进行说明 @exception 异常类型 异常说明 1. 仅用于方法;2.方法没有throws抛出则不能写;3. 可并列多个

2. 用于编译时格式检查的注解

注解 作用 格式要求 备注
@Override 限定重写父类方法 @Override 多个作者之间用逗号隔开
@Deprecated 表示所修饰元素已过时,通常因为所修饰的结构或存在更好的选择 @Deprecated ————————
@SuppressWarnings 抑制编译器警告 @SuppressWarnings
(“警告类型”)
警告类型有deprecation、unchecked、fallthrough、path等

下面这段代码展示了以上注解的用法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
/**
* @author LiYuexi
* @version v0.1
* @see Dog
* @since 7 March 2023
*/
public class Animal{
//名字
@SuppressWarnings("unused")
private int name;
//种类
@SuppressWarnings("unused")
private int species;
//健康值
@SuppressWarnings("unused")
private int health;

/**
* @param food
*/
public void eat(String food){
System.out.println("Animal eats " + food);
}

public void walk(){
System.out.println("Animal walks");
}

/**
* @return
*/
public int getHealth() {
return health;
}

@Deprecated
public void suicide(){
System.out.println("Animal suicides");
}
}

class Dog extends Animal{

@Override
public void eat(String food){
System.out.println("Dog eats " + food);
}
@Override
public void walk(){
System.out.println("Dog walks");
}

public void play(){
System.out.println("Dog plays something");
}

/**
* @throws Exception
*/
public boolean attack() throws Exception{
int health = getHealth();
if(health < 0||health > 100) throw new Exception();
if(getHealth() >= 60){
return true;
}else{
return false;
}
}
}

3. 用于替代配置文件的注解

Servlet3.0提供了注解,使得不需要在web.xml文件中进行Servlet的配置

1
2
3
4
5
6
7
8
<servlet>
<servlet-name>LoginServlet</servlet-name>
<servlet-class>com.servlet.LoginServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>LoginServlet</servlet-name>
<url-pattern>/login</url-pattern>
</servlet-mapping>

使用@WebServlet注解替代上述冗长的XML配置信息,如下所示。
1
2
3
4
5
6
7
8
9
10
11
12
@WebServlet(name = "LoginServlet", value = "/login")
public class LoginServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {

}

@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {

}
}

二、自定义注解

  • 使用@interface声明自定义Annotation,本质上就是一个类
  • 自定义注解自动继承java.lang.annotation.Annotation接口
  • 自定义Annotation可以有成员变量,其类型可为八种基本数据类型、String类型、Class类型、enum类型、Annotation类型以及上述类型的数组形式
  • 成员变量声明为无参数方法的形式,如String value();
  • 没有成员变量的Annotation称为标记,包含成员变量的Annotation称为元数据Annotation
  • 成员变量如果只有一个,一般命名为value,也可以用别的名字
  • 成员变量如果有多个,可以用以value命名的数组,如String[] value();,也可用别的名字
  • 使用 default 默认值 来为成员变量指定默认值,如String value() default “hello”;
  • 如果没有给成员变量指定默认值,当在使用Annotation时,必须指定参数值,如@MyAnnotation(value = “hello”)
  • 可以没有成员变量

下面是一个例子

1
2
3
4
5
6
7
8
public @interface MyAnnotation {
String value() default "hello";
}

@MyAnnotation(value = "hello")
class Test{

}

三、元注解(meta-annotation)

  • 元Annotation用于修饰其他Annotation
  • JDK5.0提供了4个标准元注解,分别是@Retention、@Target、@Documented、@Inherited
注解 作用 格式要求 备注
@Retention 指定被修饰Annotation的生命周期 @Retention(状态值) 状态值有三种,见后文
@Target 指定被修饰的Annotation能用于修饰哪些程序元素 @Target({若干程序元素}) 可指定元素见后文
@Documented 指定被修饰的Annotation将被javadoc工具提取成文档 @Documented 定义为@Documented的注解必须设置Retention的值为RUNTIME
@Inherited 指定被修饰的Annotation具有继承性 @Inherited 所修饰的Annotation修饰的类的子类自动具有该注解

@Retention的成员变量可被指定为三种值:

  1. RetentionPolicy.SOURCE 意味所修饰的注解只在编译阶段起作用,不会保留在class文件中
  2. RetentionPolicy.CLASS 默认行为,所修饰注解会被保留在class文件中,但不会保留在运行阶段
  3. RetentionPolicy.RUNTIME 保留在class文件中,同时还保留在运行阶段,加载在内存当中,能通过反射获取

@Target的成员变量是一个数组,可被指定为一个或多个以下值:

  1. ElementType.TYPE
  2. ElementType.FIELD
  3. ElementType.METHOD
  4. ElementType.PARAMETER
  5. ElementType.CONSTRUCTOR
  6. ElementType.LOCAL_VARIABLE

关于@Documented,下面是一个例子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import java.lang.annotation.*;

@Inherited
@Documented
@Target({ElementType.TYPE,ElementType.FIELD,ElementType.METHOD})
@Retention(RetentionPolicy.CLASS)
public @interface MyAnnotation {
String value() default "hello";
}

@MyAnnotation(value = "hello")
class Test{

}

//由于@Inherited的存在,kid类自动继承其父类Test的注解
class kid extends Test{

}

以下是JDK 8的新特性

四、可重复注解

在JDK 8以前,只能如此实现重复注解:

1
2
3
4
5
6
7
8
9
10
11
12
13
public @interface MyAnnotations {
MyAnnotation[] value();
}

@interface MyAnnotation {
String value() default "hello";
}

@MyAnnotations({@MyAnnotation("hello"),@MyAnnotation("world")})
class Test{

}

jdk 8以来,可以这么实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
import java.lang.annotation.*;

import static java.lang.annotation.ElementType.*;

@Retention(RetentionPolicy.RUNTIME)
@Target({TYPE,FIELD,METHOD,PARAMETER,CONSTRUCTOR,LOCAL_VARIABLE})
@interface MyAnnotations{
MyAnnotation[] value();
}

@Repeatable(MyAnnotations.class)
@Retention(RetentionPolicy.RUNTIME)
@Target({TYPE,FIELD,METHOD,PARAMETER,CONSTRUCTOR,LOCAL_VARIABLE})
@interface MyAnnotation {
String value() default "hello";
}

@MyAnnotation("hello")
@MyAnnotation("world")
class Test{

}

注:
1.在MyAnnotation上声明Repeatable,成员变量值为MyAnnotations.class
2.保证MyAnnotation的元注解@Retention和@Target和MyAnnotations的@Retention和@Target一模一样

五、类型注解

  • JDK 8之后,@Target的参数类型ElementType枚举值多了两个:TYPE_PARAMETER和TYPE_USE
  • JDK 8以前,注解只能写在声明的地方;JDK 8以后,注解可以写在任何地方,只要@Target值中包含TYPE_PARAMETER或TYPE_USE。
  1. ElementType.TYPE_PARAMETER 表示该注解能写在类型变量的声明语句中
  2. ElementType.TYPE_USE 表示该注解能写在使用类型的任何语句中

下面是ElementType.TYPE_PARAMETER的用法

1
2
3
4
5
6
7
8
9
10
import java.lang.annotation.ElementType;
import java.lang.annotation.Target;

public class Generic<@MyAnnotation() T> {
}

@Target({ElementType.TYPE_PARAMETER})
@interface MyAnnotation{
String value() default "hello";
}

下面是ElementType.TYPE_USE的用法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import java.lang.annotation.ElementType;
import java.lang.annotation.Target;
import java.util.ArrayList;

public class Generic<@MyAnnotation() T> {
public void show() throws @MyAnnotation RuntimeException{
ArrayList<@MyAnnotation String> list = new ArrayList<>();
int num = (@MyAnnotation int) 10L;

}
}

@Target({ElementType.TYPE_USE})
@interface MyAnnotation{
String value() default "hello";
}