1.Annotation基本信息
1.1:背景
从jdk 5 开始,java增加了对元数据(MetaData)的支持,也就是Annotation(注解),元数据就是Annotation
1.2:概念:
Annotation是一个接口,程序可以通过反射来获取指定程序元素的Annotation对象,然后通过Annotation对象来取得注解里的元数据。
1.3:作用:
为程序元素(类、方法、成员变量等)设置元数据后,开发人员可以在不改变原有逻辑的情况下,在源文件中嵌入一些补充信息,这些元数据可以在编译、类加载、运行时被读取,并执行相应的处理。可以做额外的功能,可以分析代码质量等。
1.4:形式:
Annotation以"name = value"形式显示,如果
2.Annotation分类
2.1基本Annotation
@Override 限定重写父类的方法
@Deprecated 标示已过时
@SuppressWarnings 抑制编译器警告
@FunctionalInterface 用于编译级错误检查,提醒编译器去检查该接口是否 仅包含一个抽象方法
//压制警告
@SuppressWarnings("unchecked")
public class People {
public static void main(String[] args){
Set set = new TreeSet();
set.add("a");
/**
* 将一个无泛型的Set传递给了varagMethod方法,此时就有可能造成堆污染
* 把一个不带泛型的对象赋给一个带泛型的变量时候,往往会发生这种"堆污染(Heap Pollution)"情况
*/
eat(set);
}
public static void eat(Set<Integer> objects) {
/**
* Exception in thread "main" java.lang.ClassCastException:
* java.base/java.lang.String cannot be cast to java.base/java.lang.Integer
*/
objects.add(10);// ClassCastException thrown
}
/**
* 吃的方法
*/
public void eat(){
System.out.println("people eat method");
}
public void eat(List<String> fruit){
//
List list = fruit;
}
/**
* 狗类
*/
class man extends People {
/**
* 规定狗吃的方法继承自动物,就加上该@Override注解
*/
@Override
public void eat(){
System.out.println("man eat method");
}
/**
* 定义标识该方法已过期,以后不建议使用该方法
*/
@Deprecated
public void go(){
}
}
/**
* drinkwater接口只允许有一个抽象方法
*/
@FunctionalInterface
interface drinkwater{
void warter();
}
}
2.2jdk的元Annotaion
1:@Retention 保留; 记忆力
@Retention只能用于修饰一个Annotation定义,用于指定被修饰的Annotation可以保留多长时间
@Retention包含了一个RetentionPolicy类型的value成员变量,所以使用@Retention时必须为该
value成员变量指定值
value成员变量的值只能是以下三个
a: RetentionPolicy.CLASS
@Retention(value = RetentionPolicy.CLASS)
@Retention(RetentionPolicy.CLASS)
编译器把Annotation记录在class文件中,当运行java程序时,jvm不再保留Annotation
b: RetentionPolicy.RUNTIME
@Retention(value = RetentionPolicy.RUNTIME)
@Retention(RetentionPolicy.RUNTIME)
编译器把Annotation记录在class文件中,当运行java程序时,jvm会保留Annotation,程序可以
通过反射获取该Annotation信息
c:RetentionPolicy.SOURCE
@Retention(value = RetentionPolicy.SOURCE)
@Retention(RetentionPolicy.SOURCE)
Annotation只保留在源代码中,编译器直接丢弃这种Annotation
2:@Target (服务的)对象; 目标
@Target只能用于修饰一个Annotation定义,它用于指定被修饰的Annotation能用于修饰那些程序单元
a:ElementType.ANNOTATION_TYPE
指定该策略的Annotation只能修饰Annotation
b:ElementType.CONSTRUCTOR
指定该策略的Annotation只能修饰构造器
c:ElementType.FIELD
指定该策略的Annotation只能修饰成员变量
d:ElementType.LOCAL_VARIABLE
指定该策略的Annotation只能修饰局部变量
e:ElementType.METHOD
指定该策略的Annotation只能修饰方法定义
f:ElementType.PACKAGE
指定该策略的Annotation只能修饰包定义
g:ElementType.PARAMETER
指定该策略的Annotation只能修饰参数
h:ElementType.TYPE
指定该策略的Annotation只能修饰 类 接口 枚举
3:@Documented
注解表明这个注解应该被 javadoc工具记录. 默认情况下,javadoc是不包括注解的.
但如果声明注解时指定了 @Documented,则它会被 javadoc 之类的工具处理,
所以注解类型信息也会被包括在生成的文档中
4:@Inherited
这是一个稍微复杂的注解类型. 它指明被注解的类会自动继承. 更具体地说,
如果定义注解时使用了 @Inherited 标记,然后用定义的注解来标注另一个父类,
父类又有一个子类(subclass),则父类的所有属性将被继承到它的子类中.
2.3自定义Annotation
定义新的Annotation类型使用@interface关键字,它用于定义新的Annotation类型。定义一个新的 Annotation类型与定义一个接口非常像,Annotation前面比接口前面多一个@,如下代码可定义一个简单的Annotation:
public @interface Login {
}
定义了该Annotation之后,就可以在程序任何地方来使用该Annotation,使用Annotation时的语法非常类似于public、final这样的修饰符。通常可用于修饰程序中的类、方法、变量、接口等定义,通常我们会把Annotation放在所有修饰符之前,而且由于使用Annotation时可能还需要为其成员变量指定值,因而Annotation长度可能比较长,所以通常把Annotation另放一行,如下程序所示:
/**
* 定义一个Annotation
*/
/**
* 定义一个Annotation
*/
public @interface Login {
}
class LoginTest{
/**
* 使用Annotation
*/
@Login
public void login(){
}
}
Annotation不仅可以是这种简单Annotation,Annotation还可以带成员变量,Annotation的成员变量在Annotation定义中以无参数方法的形式声明。其方法名和返回值定义了该成员的名字和类型。如下代码可以定义一个有成员变量的Annotation:
/**
* 定义一个注解
*/
public @interface Login {
//定义两个成员变量
String username();
String password();
}
一旦在Annotation里定义了成员变量之后,使用该Annotation时应该为该Annotation的成员变量指定值,如下代码所示:
/**
* 定义一个注解
*/
public @interface Login {
//定义两个成员变量
String username();
String password();
}
class LoginTest{
/**
* 使用注解
*/
@Login(username="jack", password="123456")
public void login(){
}
}
我们还可以在定义Annotation的成员变量时为其指定初始值,指定成员变量的初始值可使用default关键字,如下代码:
/**
* 定义一个注解
*/
public @interface Login {
//定义两个成员变量
//以default为两个成员变量指定初始值
String username() default "jack";
String password() default "123456";
}
如果为Annotation的成员变量指定了默认值,使用该Annotation则可以不为这些成员变量指定值,而是直接使用默认值。如下代码:
/**
* 定义一个注解
*/
public @interface Login {
//定义两个成员变量
//以default为两个成员变量指定初始值
String username() default "jack";
String password() default "123456";
}
class LoginTest{
/**
* 使用注解
* 因为它的成员变量有默认值,所以可以无须为成员变量指定值,而直接使用默认值
*/
@Login
public void login(){
}
}
2.3提取Annotation的信息
当开发者使用Annotation修饰了类、方法、Field等成员之后,这些Annotation不会自己生效,必须由开发者提供相应的工具来获取并处理Annotation信息。
java使用Annotation接口来代表程序元素前面的注释,该接口是所有Annotation类型的父接口.
Java 5 在java.lang.reflect包下新增了AnnotateElement接口,该接口代表程序中可以接受注释的程序元素,该接口主要有如下几个实现类(注意以下是类):
a: Class:类定义。
b: Constructor:构造器定义。
c: Field:类的成员变量定义。
d: Method:类的方法定义。
e: Package:类的包定义。
java.lang.reflect包下主要包含一些实现反射功能工具类,实际上,java.lang.reflect包提供的反射API扩充了读取运行时Annotation的能力。当一个Annotation类型被定义为运行时Annotation后,该注解才是运行时可见,jvm才会在装载*.class文件时读取保存在class文件中的Annotation。
AnnotatedElement接口是所有程序元素(如Class、Method、Constructor)的父接口,所以程序通过反射获取了某个类的AnnotatedElement对象(如Class、Method、Constructor)之后,程序就可以调用该对象的如下三个方法来访问Annotation信息:
getAnnotation(Class<T> annotationClass);
//返回该程序元素上存在的、指定类型的注释,如果该类型的注释不存在,则返回null。
Annotation[] getAnnotations();
//返回该程序元素上存在的所有注释。
boolean isAnnotationPresent(Class<? extends Annotation> annotationClass);
//判断该程序元素上是否包含指定类型的注解,存在则返回true,否则返回false。
import java.lang.annotation.*;
import java.lang.reflect.Method;
//@Retention注解指定Login注解可以保留多久
@Retention(RetentionPolicy.RUNTIME)
//@Target注解指定注解能修饰的目标(只能是方法)
@Target(ElementType.METHOD)
@interface Login{
String userName() default "jack";
String passWord() default "123456";
}
public class Briup {
public static void main(String[] args){
try {
//通过反射获取info方法类
Method method = Briup.class.getMethod("study");
//判断该方法上是否存在@Login注释
boolean isAnnotationPresent = method.isAnnotationPresent(Login.class);
if(isAnnotationPresent){
System.out.println("info方法上存在@Login注释");
}else {
System.out.println("info方法上不存在@Login注释");
}
/**
* 获取方法上的所有注释
*/
Annotation[] annotations = method.getAnnotations();
for(Annotation a : annotations){
//如果是@Login注释,则强制转化,并调用username方法,和password方法。
if(a!=null && a instanceof Login){
String userName = ((Login) a).userName();
String passWord = ((Login) a).passWord();
System.out.println("userName: "+userName);
System.out.println("passWord: "+passWord);
}
System.out.println(a);
}
} catch (NoSuchMethodException e) {
e.printStackTrace();
}
}
@Login
@Deprecated
public void study(){
}
}
下面分别介绍两个使用Annotation的例子,第一个Annotation @Test没有任何成员变量,仅是一个标记Annotation,它的作用是标记哪些方法是可测试的。
import java.lang.annotation.*;
import java.lang.reflect.Method;
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
@interface Test{
}
class Junit {
@Test
public static void test1(){
}
public static void test2(){
}
public static void test3(){
}
@Test
public static void test4(){
}
}
public class Briup {
public static void main(String[] args){
//1.1通过反射获取类
try {
Class<?> forName = Class.forName("demo_annotation.Junit");
//1.2获取该类自身声明的所有方法
Method[] methods = forName.getDeclaredMethods();
int checkCount = 0; //测试的数量
int uncheckCount = 0; //未测试的数量
for (Method method : methods) {
if(method.isAnnotationPresent(Test.class)){
checkCount++;
}else{
uncheckCount++;
}
}
System.out.println("测试的方法有" + checkCount);
System.out.println("未测试的方法有" + uncheckCount);
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
}
输出结果:
测试的方法有2
未测试的方法有2
网友评论