> 文章列表 > Java注解

Java注解

Java注解

什么是注解

注解是放在Java源码的类、方法、字段、参数前的一种特殊“注释”
注释会被编译器直接忽略,注解则可以被编译器打包进入class文件,因此,注解是一种用作标注的“元数据”
作用:从JVM的角度看,注解本身对代码逻辑没有任何影响,如何使用注解完全由工具决定

最重要的一点:Annotation是被动的元数据,永远不会有主动行为。但凡Annotation起作用的场合都是有一个执行机制/调用者通过反射获得了这个元数据然后根据它采取行动

Java注解分类(三类)

1.由编译器使用的注解

这类注解不会被编译进入.class文件,它们在编译后就被编译器扔掉了
@Override 让编译器检查该方法是否正确地实现了覆写
@SuppressWarnings 告诉编译器忽略此处代码产生的警告

2.由工具处理.class文件使用的注解

比如有些工具会在加载class的时候,对class做动态修改,实现一些特殊的功能。这类注解会被编译进入.class文件,但加载结束后并不会存在于内存中。这类注解只被一些底层库使用,一般不必自己处理

3.在程序运行期能够读取的注解

它们在加载后一直存在于JVM中,这也是最常用的注解
例如:一个配置了@PostConstruct的方法会在调用构造方法后自动被调用(这是Java代码读取该注解实现的功能,JVM并不会识别该注解)

注解(Annotation)

JDK 1.5开始增加了Annotation,表示注解(也被翻译为注释),但它学注释不同。Annotation是java.lang包下的一个接口,代码里的特殊标记,这些标记可以在编译、类加载、运行时被读取,并执行相应的处理,它可用于类、构造方法、成员变量、方法、参数等的声明中

使用Annotation注解不会影响程序运行,但是会对编译器警告灯辅助工具产生影响
(1)注解(Annotation):Java语言用于工具处理的标注
(2)注解可以配置参数,没有指定配置的参数使用默认值
(3)如果参数名称是value,且只有一个参数,那么可以省略参数名称

定义一个注解时,还可以定义配置参数。配置参数可以包括:
(1)所有基本类型
(2)String
(3)枚举类型
(4)基本类型、String以及枚举的数组

因为配置参数必须是常量,所以,上述限制保证了注解在定义时就已经确定了每个参数的值。注解的配置参数可以有默认值,缺少某个配置参数时将使用默认值
此外,大部分注解会有一个名为value的配置参数,对此参数赋值,可以只写常量,相当于省略了value参数。如果只写注解,相当于全部使用默认值

1.内置注解

Java中的内置注解位于java.lang包下,它包含3个基本的注解

(1)@Override:限定重写父类的方法

/*** @since 1.5*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.SOURCE)public @interface Override {
}

(2)@Deprecated:标示已过时

/*** @since 1.5*/
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(value={CONSTRUCTOR, FIELD, LOCAL_VARIABLE, METHOD, PACKAGE, PARAMETER, TYPE})
public @interface Deprecated {
}

(3)@SuppressWarnings:抑制编译器警告

/*** @since 1.5*/
@Target({TYPE, FIELD, METHOD, PARAMETER, CONSTRUCTOR, LOCAL_VARIABLE})
@Retention(RetentionPolicy.SOURCE)
public @interface SuppressWarnings {String[] value();
}

(4)@SafeVarargs

参数安全类型注解。目的是提醒开发者不要用参数做一些不安全的操作,它的存在会阻止编译器产生unchecked这样的警告,它是在Java 1.7的版本中加入的

2.元注解

有一些注解可以修饰其他注解,这些注解就称为元注解(meta annotation)。Java标准库已经定义了一些元注解,只需要使用元注解,通常不需要自己去编写元注解

@Target(注解使用地方)

最常用的元注解是@Target。使用@Target可以定义Annotation能够被应用于源码的哪些位置

ElementType 使用范围
ElementType.PACKAGE
ElementType.TYPE 类或接口
ElementType.FIELD 字段
ElementType.CONSTRUCTOR 构造方法
ElementType.METHOD 方法
ElementType.PARAMETER 方法参数
ElementType.LOCAL_VARIABLE 局部变量

定义注解@Report可用在方法上,必须添加一个@Target(ElementType.METHOD)

@Target(ElementType.METHOD)
public @interface Report {int type() default 0;String level() default "info";String value() default "";
}

定义注解@Report可用在方法或字段上,可以把@Target注解参数变为数组{ ElementType.METHOD, ElementType.FIELD }

@Target({ElementType.METHOD,ElementType.FIELD
})
public @interface Report { ... }

@Retention(生命周期)

另一个重要的元注解@Retention定义了Annotation的生命周期

枚举类RetentionPolicy中的枚举常量

RetentionPolicy枚举常量 描述
RetentionPolicy.SOURCE 仅编译期;不编译Annotation到类文件中(有效范围最小)
RetentionPolicy.CLASS 仅class文件(默认);编译Annotation到类文件中,但是在运行时不加载Annotation到JVM中
RetentionPolicy.RUNTIME 运行期;运行时加载Annotation到JVM中(有效范围最大)

如果@Retention不存在,则该Annotation默认为CLASS。因为通常自定义的Annotation都是RUNTIME,所以,务必要加上@Retention(RetentionPolicy.RUNTIME)这个元注解

@Repeatable(是否可多次注解)

使用@Repeatable这个元注解可以定义Annotation是否可重复

@Repeatable(Reports.class)
@Target(ElementType.TYPE)
public @interface Report {int type() default 0;String level() default "info";String value() default "";
}@Target(ElementType.TYPE)
public @interface Reports {Report[] value();
}

经过@Repeatable修饰后,在某个类型声明处,就可以添加多个@Report注解

@Report(type=1, level="debug")
@Report(type=2, level="warning")
public class Hello {}

@Inherited(子类是否可继承父类注解,仅@Target)

使用@Inherited定义子类是否可继承父类定义的Annotation。@Inherited仅针对@Target(ElementType.TYPE)类型的Annotation有效,并且仅针对class的继承,对interface的继承无效

@Decumented(文档)

指示某一类型的注释通过javadoc和类似的默认工具进行文档化

3.自定义注解

Java语言使用@interface语法来定义注解(Annotation)
关键字@interface,隐含意思是继承了java.lang.annotation.Annotation接口

public @interface Report {int type() default 0;String level() default "info";String value() default "";
}

注解的参数类似无参数方法,可以用default设定一个默认值(强烈推荐)。最常用的参数应当命名为value

(1)用@interface定义注解

public @interface MyAnnotation {  }

(2)添加参数、默认值

定义只包含一个成员的Annotation类型

public @interface MyAnnotation {int type() default 0;String level() default "info";String value() default "";
}

把最常用的参数定义为value(),推荐所有参数都尽量设置默认值。

(3)用元注解配置注解

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface Report {int type() default 0;String level() default "info";String value() default "";
}

其中,必须设置@Target和@Retention,@Retention一般设置为RUNTIME,因为自定义的注解通常要求在运行期读取。一般情况下,不必写@Inherited和@Repeatable

总结
(1)Java使用@interface定义注解
(2)可定义多个参数和默认值,核心参数使用value名称
(3)必须设置@Target来指定Annotation可以应用的范围
(4)应当设置@Retention(RetentionPolicy.RUNTIME)便于运行期读取该Annotation

处理注解

Java的注解本身对代码逻辑没有任何影响。根据@Retention的配置
(1)SOURCE类型的注解在编译期就被丢掉了
(2)CLASS类型的注解仅保存在class文件中,它们不会被加载进JVM
(3)RUNTIME类型的注解会被加载进JVM,并且在运行期可以被程序读取
如何使用注解完全由工具决定。SOURCE类型的注解主要由编译器使用,因此一般只使用,不编写。CLASS类型的注解主要由底层工具库使用,涉及到class的加载,一般很少用到。只有RUNTIME类型的注解不但要使用,还经常需要编写

注解定义后也是一种class,所有的注解都继承自java.lang.annotation.Annotation,因此,读取注解,需要使用反射API

Java提供的使用反射API读取Annotation的方法包括:
判断某个注解是否存在于Class、Field、Method或Constructor(isAnnotationPresent(Class))
(1)Class.isAnnotationPresent(Class)
(2)Field.isAnnotationPresent(Class)
(3)Method.isAnnotationPresent(Class)
(4)Constructor.isAnnotationPresent(Class)

//判断@Report是否存在于Person类:
Person.class.isAnnotationPresent(Report.class);

使用反射API读取(获取)Annotation注解(getAnnotation(class))
(1)Class.getAnnotation(Class)
(2)Field.getAnnotation(Class)
(3)Method.getAnnotation(Class)
Constructor.getAnnotation(Class)

// 获取Person定义的@Report注解:
Report report = Person.class.getAnnotation(Report.class);
int type = report.type();
String level = report.level();

使用反射API读取Annotation有两种方法
(1)先判断Annotation是否存在,如果存在,就直接读取
(2)直接读取Annotation,如果Annotation不存在,将返回null

使用注解

注解如何使用,完全由程序自己决定。例如,JUnit是一个测试框架,它会自动运行所有标记为@Test的方法

@Range注解,用它来定义一个String字段的规则(字段长度满足@Range的参数定义)

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface Range {int min() default 0;int max() default 255;
}

在某个JavaBean中,可以使用该注解

public class Person {@Range(min=1, max=20)public String name;@Range(max=10)public String city;
}

但是,定义了注解,本身对程序逻辑没有任何影响。必须自己编写代码来使用注解。这里,编写一个Person实例的检查方法,它可以检查Person实例的String字段长度是否满足@Range的定义

void check(Person person) throws Exception {// 遍历所有Field:for (Field field : person.getClass().getFields()) {// 获取Field定义的@Range:Range range = field.getAnnotation(Range.class);// 如果@Range存在:if (range != null) {// 获取Field的值:Object value = field.get(person);// 如果值是String:if (value instanceof String) {String s = (String) value;// 判断值是否满足@Range的min/max:if (s.length() < range.min() || s.length() > range.max()) {throw new IllegalArgumentException("Invalid field: " + field.getName());}}}}
}

这样一来,通过@Range注解,配合check()方法,就可以完成Person实例的检查。注意检查逻辑完全是自己编写的,JVM不会自动给注解添加任何额外的逻辑


上一篇:Java网络通信                        下一篇:Java 1.8新特性