注解是提供一种为程序元素设置元数据的方法。
程序元素就是指接口、类、属性、方法;
元数据就是描述数据的数据。
注解就是Java代码里的特殊标记,比如:@Override、@Test等,作用是:让其他程序根据注解信息来决定怎么执行该程序。
元数据是添加到程序元素如方法、字段、类和包上的额外信息,注解就是一种载体形式。
注解的分类
1.自定义注解
public @interface 注解名 {
public 属性类型 属性名() default 默认值;
}
如果注解中只有一个value属性(特殊属性名),使用注解时,value名称可以不写!
例如:有一个注解:
public @interface MyTest1 {
public String value();
}
可以写成:
@MyTest1("hello")
public class MyTestAnnotation {
}
如果再新增一个方法,且没有返回默认值:
public @interface MyTest1 {
public String value();
public String name();
}
那么用注解不能省略属性名:
@MyTest1(value = "hello", name = "world")
public class MyTestAnnotation {
}
通过编译上述的MyTest1.java文件我们可以看到:
显而易见的,注解本质上就是一个接口,继承了java.lang.annotation.Annotation,这就是为什么内部的修饰符只能是public,即使不写也默认也是public,而接口方法的默认访问权限就是pubilc。注解是不能继承也不能实现其他类或接口的,它本身就是一个元数据。
@注解(...):其实就是一个实现类对象,实现了该注解以及Annotation接口。
2.元注解
定义:用于定义注解的注解,通常用于注解的定义上,标明该注解的使用范围、生效范围等。
包含以下五种:@Retention、@Target、@Documented、@Inherited、@Repeatable。
1.@Target注解★★★★★
作用:声明被修饰的注解只能在哪些位置使用。
定义如下:
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Target {
/**
* Returns an array of the kinds of elements an annotation interface
* can be applied to.
* @return an array of the kinds of elements an annotation interface
* can be applied to
*/
ElementType[] value();
}
可见value是一个数组,可以有多个取值,说明同一个注解可以同时用于标注在不同的元素上。
同时value的值可取:
这里定义一个MyTest2注解和一个MyTestAnnotation2类用于测试:
@Target({ElementType.TYPE, ElementType.CONSTRUCTOR})
public @interface MyTest2 {
}
@MyTest2
public class MyTestAnnotation2 {
@MyTest2 //报错,'@MyTest2' not applicable to field
private String name;
@MyTest2
public MyTestAnnotation2() {
}
}
2.@Retention注解★★★★★
作用:声明注解的保留周期。
定义如下:
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Retention {
/**
* Returns the retention policy.
* @return the retention policy
*/
RetentionPolicy value();
}
从编写Java代码到运行主要周期为源文件
→ Class文件
→ 运行时数据
,@Retention则标注了自定义注解的信息要保留到哪个阶段,分别对应的value取值为SOURCE
→CLASS
→RUNTIME
。
RetentionPolicy是个枚举类,有三种策略:
SOURCE:只作用在源码阶段,字节码文件中不存在,生成的class文件中就没有该信息了。
CLASS(默认值):class文件中会保留注解,但是jvm加载运行时就没有了。
RUNTIME(开发常用):一直保留到运行阶段。
下面通过例子来验证一下:
定义三种不同策略的注解和一个测试类,
@Retention(RetentionPolicy.SOURCE)
public @interface SourceTest {
}
@Retention(RetentionPolicy.CLASS)
public @interface ClassTest {
}
@Retention(RetentionPolicy.RUNTIME)
public @interface RuntimeTest {
}
public class MyTestAnnotation3 {
@SourceTest
public void sourceTest(){}
@ClassTest
public void classTest(){};
@RuntimeTest
public void runtimeTest(){};
}
通过反编译获得MyTestAnnotation3类的字节码:
public void sourceTest();
descriptor: ()V
flags: (0x0001) ACC_PUBLIC
Code:
stack=0, locals=1, args_size=1
0: return
LineNumberTable:
line 6: 0
LocalVariableTable:
Start Length Slot Name Signature
0 1 0 this Lcom/annotation/MyTestAnnotation3;
public void classTest();
descriptor: ()V
flags: (0x0001) ACC_PUBLIC
Code:
stack=0, locals=1, args_size=1
0: return
LineNumberTable:
line 8: 0
LocalVariableTable:
Start Length Slot Name Signature
0 1 0 this Lcom/annotation/MyTestAnnotation3;
RuntimeInvisibleAnnotations:
0: #17()
com.annotation.ClassTest
public void runtimeTest();
descriptor: ()V
flags: (0x0001) ACC_PUBLIC
Code:
stack=0, locals=1, args_size=1
0: return
LineNumberTable:
line 10: 0
LocalVariableTable:
Start Length Slot Name Signature
0 1 0 this Lcom/annotation/MyTestAnnotation3;
RuntimeVisibleAnnotations:
0: #20()
com.annotation.RuntimeTest
我们可以看到:
1. 编译器并没有记录下sourceTest方法的注解信息
2. 编译器分别使用了RuntimeInvisibleAnnotations和RuntimeVisibleAnnotations属性去记录了classTest和runtimeTest的注解信息。
3.@Documented注解★
作用:生成文档信息的时候保留注解,对类作辅助说明。
定义如下:
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Documented {
}
这里不在赘述,可以参考:java 注解示例(@Documented)。
4.@Inherited注解★★
作用:当一个类使用了带有
@Inherited
注解的注解时,这个注解将被这个类的所有子类继承。这里需要注意的是只有当子类继承父类的时候,注解才会被继承,类实现接口,或者接口继承接口,都是无法获得父接口上的注解声明的。
定义如下:
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Inherited {
}
示例:
假设我们有一个注解 @InheritedTest
,我们希望这个注解能够被继承,在定义一个使用该注解的父类People,然后子类Man继承People:
@Retention(RetentionPolicy.RUNTIME)
@Inherited
public @interface InheritedTest {
}
@InheritedTest
public class People {
//
}
public class Man extends People{
//
}
再通过反射来检查注解是否被继承:
public class Man extends People{
public static void main(String[] args) {
Class<Man> manClass = Man.class;
Annotation[] annotations = manClass.getAnnotations();
for(Annotation annotation : annotations){
System.out.println(annotation);
}
}
}
//输出:@com.annotation.InheritedTest()
5.@Repeatable注解★★
作用:允许开发者在同一个程序元素上多次使用相同的注解。
定义如下:
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Repeatable {
/**
* Indicates the <em>containing annotation interface</em> for the
* repeatable annotation interface.
* @return the containing annotation interface
*/
Class<? extends Annotation> value();
}
这是java8之后引入的,在此之前,如果需要模拟重复注解的效果,可以使用注解的数组属性:
在注解中定义一个数组属性来存储多个值,这样就可以在单个注解实例中表示多个值。这种方法不需要额外的注解,但是它并不真正地重复注解本身,而是在单个注解中存储多个值。
模拟用户角色,这里定义了一个注解,其中包含一个角色名的数组:
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface Roles {
Role[] value();
}
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface Role {
String value();
}
使用时可以这样:
@Roles({
@Role("admin"),
@Role("user")
})
public class Person {
//
}
引入之后,可以这么写:
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface Roles {
Role[] value(); // 必须叫value,这是规定的属性名
}
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Repeatable(Roles.class)
public @interface Role {
String value();
}
这样就可以在类上重复使用@Role
:
@Role("admin")
@Role("user")
public class Person {
//
}
3.标准注解
JDK中内置了以下注解:
1. @Override:标记一个方法是覆写父类方法。
public class Parent {
public void test() {
}
}
public class Child extends Parent {
@Override
public void test() {
}
}
2. @Deprecated:用于标明被修饰的类或类成员、类方法已经废弃、过时,不建议使用。
@Deprecated
class TestClass {
//
}
3.@SuppressWarnings:指示编译器去忽略注释解中声明的警告。
抑制单类型的警告
@SuppressWarnings("unchecked")
public void test1(String item) {
@SuppressWarnings("rawtypes")
List<Object> l1 = new ArrayList();
l1.add(item);
}
抑制多类型的警告
@SuppressWarnings(value={"unchecked", "rawtypes"})
public void test2(String item) {
List<Object> l2 = new ArrayList();
l2.add(item);
}
抑制所有类型的警告
@SuppressWarnings("all")
public void test3(String item) {
List l3 = new ArrayList();
l3.add(item);
}
4.@Functionallnterface:java8支持,标识一个匿名函数或函数式接口。
@FunctionalInterface
public interface ComplexFunctionalInterface {
void doWork();
default void defaultMethod() {
System.out.println("This is a default method");
}
static void staticMethod() {
System.out.println("This is a static method");
}
}
public class Main {
public static void main(String[] args) {
ComplexFunctionalInterface cfi = () -> System.out.println("Doing work with Lambda expression");
cfi.doWork();
cfi.defaultMethod();
ComplexFunctionalInterface.staticMethod();
}
}
在这个例子中,ComplexFunctionalInterface
是一个函数式接口,它包含一个抽象方法doWork()
,一个默认方法defaultMethod()
和一个静态方法staticMethod()
。我们可以使用Lambda表达式来创建这个接口的实例,并调用这些方法。
注解的解析
注解的解析,就是判断类上、方法上、成员变量上是否存在注解,并把注解里的内容给解析出来。
想要对注解中的数据进行解析,需要借助:AnnotatedElement接口,Class、Method、Field,Constructor都实现了AnnotatedElement接口,它们都拥有解析注解的能力。
注解解析的步骤:(注解是书写在:类、方法、变量上)
利用反射技术获取注解作用的对象:类、方法、变量、构造方法。
判断对象上是否有自定义注解存在。
有:获取对象上的自定义注解。
使用获取到的自定义注解对象,拿到注解中的属性值。
注意:注解解析必须保证自定义注解生命周期在RUNTIME(程序运行中)
示例:
定义注解MyTest4,要求如下:
包含属性:String value()
包含属性:double aaa(),默认值为100
包含属性:String[] bbb()
限制注解使用的位置:类和成员方法上
指定注解的有效范围:一直到运行时
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface MyTest4 {
String value();
double aaa() default 100;
String[] bbb();
}
定义一个类叫:Demo,在类中定义一个test1方法,并在该类和其方法上使用MyTest4注解.
@MyTest4(value = "沙威玛", aaa = 21.1, bbb = {"fantastic", "fabulous"})
public class Demo {
@MyTest4(value = "汉堡包", aaa =12.1, bbb = {"good", "nice"})
public void test1(){
}
}
定义AnnotationTest3测试类,解析Demo类中的全部注解。
@Test
public void parseClass(){
// 1.先得到Class对象
Class demoClass = Demo.class;
// 2.解析类上的注解
// 判断类上是否包含某个注解
if(demoClass.isAnnotationPresent(MyTest4.class)){
MyTest4 myTest4 = (MyTest4) demoClass.getDeclaredAnnotation(MyTest4.class);
System.out.println(myTest4.value()); // 输出:沙威玛
System.out.println(myTest4.aaa()); // 输出:21.1
System.out.println(Arrays.toString(myTest4.bbb())); // 输出:[fantastic, fabulous]
}
}
@Test
public void parseMethod() throws NoSuchMethodException{
// 1.先得到Class对象
Class demoClass = Demo.class;
Method method = demoClass.getDeclaredMethod("test1");
// 2.解析方法上的注解
// 判断方法对象上是否包含某个注解
if(method.isAnnotationPresent(MyTest4.class)){
MyTest4 myTest4 = method.getDeclaredAnnotation(MyTest4.class);
System.out.println(myTest4.value()); // 输出:汉堡包
System.out.println(myTest4.aaa()); // 输出:12.1
System.out.println(Arrays.toString(myTest4.bbb())); // 输出:[good, nice]
}
}