:::tips 注解是JDK1.5版本开始引入的一个特性,用于对代码进行说明,可以对包、类、接口、字段、方法参数、局部变量等进行注解。它是框架学习和设计者必须掌握的基础。 :::
注解基础
主要作用:
- 生成文档,通过代码里标识的元数据生成javadoc文档。
- 编译检查,通过代码里标识的元数据让编译器在编译期间进行检查验证。
- 编译时动态处理,编译时通过代码里标识的元数据动态处理,例如动态生成代码。
运行时动态处理,运行时通过代码里标识的元数据动态处理,例如使用反射注入实例。
常见分类:
Java自带的标准注解,包括@Override、@Deprecated和@SuppressWarnings,分别用于标明重写某个方法、标明某个类或方法过时、标明要忽略的警告,用这些注解标明后编译器就会进行检查。
- 元注解,元注解是用于定义注解的注解,包括@Retention、@Target、@Inherited、@Documented,@Retention用于标明注解被保留的阶段,@Target用于标明注解使用的范围,@Inherited用于标明注解可继承,@Documented用于标明是否生成javadoc文档。
自定义注解,可以根据自己的需求定义注解,并可用元注解对自定义注解进行注解。
Java内置注解
Java 1.5开始自带的标准注解,包括@Override、@Deprecated和@SuppressWarnings:
- @Override:表示当前的方法定义将覆盖父类中的方法
- @Deprecated:表示代码被弃用,如果使用了被@Deprecated注解的代码则编译器将发出警告
- @SuppressWarnings:表示关闭编译器警告信息
内置注解 - @Override
- 我们先来看一下这个注解类型的定义: ```java @Target(ElementType.METHOD)//修饰方法 @Retention(RetentionPolicy.SOURCE)//编译时有效 public @interface Override { }
2. 从它的定义我们可以看到,这个注解可以被用来修饰方法,并且它只在编译时有效,在编译后的class文件中便不再存在。告诉编译器被修饰的方法是重写的父类的中的相同签名的方法,编译器会对此做出检查,若发现父类中不存在这个方法或是存在的方法签名不同,则会报错。
<a name="t0m8y"></a>
#### 内置注解 - @Deprecated
1. 这个注解的定义如下:
```java
@Documented//文档化
@Retention(RetentionPolicy.RUNTIME)//运行时有效
@Target(value={CONSTRUCTOR, FIELD, LOCAL_VARIABLE, METHOD, PACKAGE, PARAMETER, TYPE})
//能够修饰构造方法、属性、局部变量、方法、包、参数、类型。
public @interface Deprecated {
}
从它的定义我们可以知道,它会被文档化,能够保留到运行时,能够修饰构造方法、属性、局部变量、方法、包、参数、类型。这个注解的作用是告诉编译器被修饰的程序元素已被“废弃”,不再建议用户使用。
内置注解 - @SuppressWarnings
这个注解我们也比较常用到,先来看下它的定义: ```java @Target({TYPE, FIELD, METHOD, PARAMETER, CONSTRUCTOR, LOCAL_VARIABLE})//修饰类型 @Retention(RetentionPolicy.SOURCE)//编译时有效 public @interface SuppressWarnings { String[] value(); }
2. 它能够修饰的程序元素包括类型、属性、方法、参数、构造器、局部变量,只能存活在源码时,取值为String[]。它的作用是告诉编译器忽略指定的警告信息.
<a name="Wn2T6"></a>
### 元注解
1. 上述内置注解的定义中使用了一些元注解(注解类型进行注解的注解类),在JDK 1.5中提供了4个标准的元注解:@Target,@Retention,@Documented,@Inherited, 在JDK 1.8中提供了两个元注解 @Repeatable和@Native。
<a name="kX5Qv"></a>
#### 元注解 - @Target
:::tips
Target注解的作用是:描述注解的使用范围(即:被修饰的注解可以用在什么地方) 。
:::
1. Target注解用来说明那些被它所注解的注解类可修饰的对象范围:注解可以用于修饰 packages、types(类、接口、枚举、注解类)、类成员(方法、构造方法、成员变量、枚举值)、方法参数和本地变量(如循环变量、catch参数),在定义注解类时使用了@Target 能够更加清晰的知道它能够被用来修饰哪些对象,它的**取值范围定义在ElementType 枚举中**。
```java
public enum ElementType {
TYPE, // 类、接口、枚举类
FIELD, // 成员变量(包括:枚举常量)
METHOD, // 成员方法
PARAMETER, // 方法参数
CONSTRUCTOR, // 构造方法
LOCAL_VARIABLE, // 局部变量
ANNOTATION_TYPE, // 注解类
PACKAGE, // 可用于修饰:包
TYPE_PARAMETER, // 类型参数,JDK 1.8 新增
TYPE_USE // 使用类型的任何地方,JDK 1.8 新增
}
元注解 - @Retention & @RetentionTarget
:::tips Reteniton注解的作用是:描述注解保留的时间范围(即:被描述的注解在它所修饰的类中可以被保留到何时) 。 :::
Reteniton注解用来限定那些被它所注解的注解类在注解到其他类上以后,可被保留到何时,一共有三种策略,定义在RetentionPolicy枚举中。 ```java public enum RetentionPolicy {
SOURCE, // 源文件保留 CLASS, // 编译期保留,默认值 RUNTIME // 运行期保留,可通过反射去获取注解信息 }
2. 为了验证应用了这三种策略的注解类有何区别,分别使用三种策略各定义一个注解类做测试。
```java
public class RetentionTest {
@SourcePolicy
public void sourcePolicy() {
}
@ClassPolicy
public void classPolicy() {
}
@RuntimePolicy
public void runtimePolicy() {
}
}
通过执行 javap -verbose RetentionTest命令获取到的RetentionTest 的 class 字节码内容如下。 ```java { public retention.RetentionTest(); flags: ACC_PUBLIC Code: stack=1, locals=1, args_size=1
0: aload_0 1: invokespecial #1 // Method java/lang/Object."<init>":()V 4: return
LineNumberTable:
line 3: 0
public void sourcePolicy(); flags: ACC_PUBLIC Code: stack=0, locals=1, args_size=1
0: return
LineNumberTable:
line 7: 0
public void classPolicy(); flags: ACC_PUBLIC Code: stack=0, locals=1, args_size=1
0: return
LineNumberTable:
line 11: 0
RuntimeInvisibleAnnotations: 0: #11()
public void runtimePolicy(); flags: ACC_PUBLIC Code: stack=0, locals=1, args_size=1
0: return
LineNumberTable:
line 15: 0
RuntimeVisibleAnnotations: 0: #14() }
4. 从 RetentionTest 的字节码内容我们可以得出以下两点结论:
- 编译器并没有记录下 sourcePolicy() 方法的注解信息;
- 编译器分别使用了 RuntimeInvisibleAnnotations 和 RuntimeVisibleAnnotations 属性去记录了classPolicy()方法 和 runtimePolicy()方法 的注解信息;
<a name="LeU9h"></a>
#### 元注解 - @Documented
:::tips
Documented注解的作用是:描述在使用 javadoc 工具为类生成帮助文档时是否要保留其注解信息。
:::
1. 以下代码在使用Javadoc工具可以生成@TestDocAnnotation注解信息。
```java
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Target;
@Documented
@Target({ElementType.TYPE,ElementType.METHOD})
public @interface TestDocAnnotation {
public String value() default "default";
}
@TestDocAnnotation("myMethodDoc")
public void testDoc() {
}
元注解 - @Inherited
:::tips Inherited注解的作用:被它修饰的Annotation将具有继承性。如果某个类使用了被@Inherited修饰的Annotation,则其子类将自动具有该注解。 :::
- 注解测试:
- 定义@Inherited注解: ```java @Inherited @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.TYPE,ElementType.METHOD}) public @interface TestInheritedAnnotation { String [] values(); int number(); }
- 使用这个注解
```java
@TestInheritedAnnotation(values = {"value"}, number = 10)
public class Person {
}
class Student extends Person{
@Test
public void test(){
Class clazz = Student.class;
Annotation[] annotations = clazz.getAnnotations();
for (Annotation annotation : annotations) {
System.out.println(annotation.toString());
}
}
}
- 输出 ```java xxxxxxx.TestInheritedAnnotation(values=[value], number=10)
2. 即使Student类没有显示地被注解@TestInheritedAnnotation,但是它的父类Person被注解,而且@TestInheritedAnnotation被@Inherited注解,因此Student类自动有了该注解。
<a name="LbJeU"></a>
#### 元注解 - @Repeatable
:::tips
允许在同一申明类型(类,属性,或方法)的多次使用同一个注解
:::
1. 重复注解
```java
@Repeatable(Authorities.class)
public @interface Authority {
String role();
}
public @interface Authorities {
Authority[] value();
}
public class RepeatAnnotationUseNewVersion {
@Authority(role="Admin")
@Authority(role="Manager")
public void doSomeThing(){ }
}
不同的地方是,创建重复注解Authority时,加上@Repeatable,指向存储注解Authorities,在使用时候,直接可以重复使用Authority注解。
元注解 - @Native
使用 @Native 注解修饰成员变量,则表示这个变量可以被本地代码引用,常常被代码生成工具使用。对于 @Native 注解不常使用,了解即可
注解与反射接口
:::tips 定义注解后,如何获取注解中的内容呢?反射包java.lang.reflect下的AnnotatedElement接口提供这些方法。这里注意:只有注解被定义为RUNTIME后,该注解才能是运行时可见,当class文件被装载时被保存在class文件中的Annotation才会被虚拟机读取。 ::: ……
自定义注解
- 定义自己的注解 ```java package com.pdai.java.annotation;
import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target;
@Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) public @interface MyMethodAnnotation {
public String title() default "";
public String description() default "";
}
- 使用注解
```java
package com.pdai.java.annotation;
import java.io.FileNotFoundException;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;
public class TestMethodAnnotation {
@Override
@MyMethodAnnotation(title = "toStringMethod", description = "override toString method")
public String toString() {
return "Override toString method";
}
@Deprecated
@MyMethodAnnotation(title = "old static method", description = "deprecated old static method")
public static void oldMethod() {
System.out.println("old method, don't use it.");
}
@SuppressWarnings({"unchecked", "deprecation"})
@MyMethodAnnotation(title = "test method", description = "suppress warning static method")
public static void genericsTest() throws FileNotFoundException {
List l = new ArrayList();
l.add("abc");
oldMethod();
}
}
- 用反射接口获取注解信息
在TestMethodAnnotation中添加Main方法进行测试: ```java public static void main(String[] args) { try {
// 获取所有methods Method[] methods = TestMethodAnnotation.class.getClassLoader() .loadClass(("com.pdai.java.annotation.TestMethodAnnotation")) .getMethods(); // 遍历 for (Method method : methods) { // 方法上是否有MyMethodAnnotation注解 if (method.isAnnotationPresent(MyMethodAnnotation.class)) { try { // 获取并遍历方法上的所有注解 for (Annotation anno : method.getDeclaredAnnotations()) { System.out.println("Annotation in Method '" + method + "' : " + anno); } // 获取MyMethodAnnotation对象信息 MyMethodAnnotation methodAnno = method .getAnnotation(MyMethodAnnotation.class); System.out.println(methodAnno.title()); } catch (Throwable ex) { ex.printStackTrace(); } } }
} catch (SecurityException | ClassNotFoundException e) {
e.printStackTrace();
} }
- 测试的输出
```java
Annotation in Method 'public static void com.pdai.java.annotation.TestMethodAnnotation.oldMethod()' : @java.lang.Deprecated()
Annotation in Method 'public static void com.pdai.java.annotation.TestMethodAnnotation.oldMethod()' : @com.pdai.java.annotation.MyMethodAnnotation(title=old static method, description=deprecated old static method)
old static method
Annotation in Method 'public static void com.pdai.java.annotation.TestMethodAnnotation.genericsTest() throws java.io.FileNotFoundException' : @com.pdai.java.annotation.MyMethodAnnotation(title=test method, description=suppress warning static method)
test method
Annotation in Method 'public java.lang.String com.pdai.java.annotation.TestMethodAnnotation.toString()' : @com.pdai.java.annotation.MyMethodAnnotation(title=toStringMethod, description=override toString method)
toStringMethod
深入理解注解
……