Java基础知识总结——注解(Annotation)

一、基本概念

官方文档对注解(Annotation)的解释为:Java 注解用于为 Java 代码提供元数据。作为元数据,注解不直接影响你的代码执行,但也有一些类型的注解实际上可以用于这一目的。Java 注解是从 Java5 开始添加到 Java 的。实际上,可以简单的将注解理解为给代码添加的一个标签,提供了了一些关于代码的信息。

二、注解的定义和使用

定义注解需要使用 @interface 关键字,示例如下:

//定义一个简单的注解
public @interface Test{
    
    
}

定义了上述注解之后,就可以在程序中的任意位置使用该注解来修饰程序中的类、方法、接口等。示例如下:

//使用 @Test 注解修饰类定义
@Test
public class MyClass{
    
    
	...
}

注解可以带有成员变量,注解的成员变量在注解的定义中以“无形参的方法”形式来声明,其方法名和返回值定义了该成员变量的名字和类型。示例如下:

//定义一个有成员变量的注解
public @interface NewTest{
    
    
	String name();
	int age();
}

如果在注解中定义了成员变量,那么在使用该注解的时候就需要为注解的成员变量赋值,赋值的方式是在注解后面的括号内以 value=”” 形式,多个属性之间用逗号隔开。示例如下:

//使用 @NewTest 注解修饰类定义
@NewTest(name="xx", age=6)
public class MyNewClass{
    
    
	...
}

在定义成员变量时,也可以用 default 关键字为成员变量指定默认值。示例如下:

//定义一个成员变量有默认值的注解
public @interface RTest{
    
    
	String name() default "myname";
	int age() default 23;
}

如果为注解的成员变量指定了默认值,则使用该注解时可以不为成员变量赋值,而是直接使用默认值。示例如下:

//使用 @RTest 注解修饰类定义
@RTest()
public class MyNewClass{
    
    
	...
}

若成员变量只有一个,则在为成员变量赋值时可以直接填写该值。示例如下:

public @interface TTest{
    
    
	int age() default 23;
}

@TTest(12)
public class MyNewClass{
    
    
	...
}

一个注解没有任何成员变量时可以省略括号。示例如下:

public @interface TTest{
    
    }

@TTest
public class MyNewClass{
    
    
	...
}

三、元注解

元注解即是给注解的注解,其作用是对普通的注解进行解释说明。
元注解有以下五个:

1、@Retention

@Retention 用于指定注解的存活时间。

  • RetentionPolicy.SOURCE:注解只保存在源代码中,注解只保留在源码阶段,在编译器进行编译时会被丢弃。
  • RetentionPolicy.CLASS:注解记录在 calss 文件中,注解只保留到编译阶段,不会被加载到虚拟机中。
  • RetentionPolicy.RUNTIME:注解记录在 calss 文件中,注解可以保留到程序运行的时候,它会被加载进入到虚拟机中,程序可以通过反射获取注解的信息。

示例如下:

//定义 Test 注解可以被保留到运行期间
@Retention(RetentionPolicy.RUNTIME)
public @interface Test{
    
    }

2、@Target

@Target 用于指定注解的修饰对象。

  • ElementType.ANNOTATION_TYPE:该注解只能修饰注解
  • ElementType.CONSTRUCTOR:该注解只能修饰构造器
  • ElementType.FIELD:该注解只能修饰成员变量
  • ElementType.LOCAL_VARIABLE:该注解只能修饰局部变量
  • ElementType.METHOD:该注解只能修饰方法
  • ElementType.PACKAGE:该注解只能修饰包
  • ElementType.PARAMETER:该注解只能修饰方法内的参数
  • ElementType.TYPE:该注解可以修饰类、接口(包括注解类型)或枚举定义。

示例如下:

//定义 Test 注解只能修饰成员变量
@Target(ElementType.FIELD)
public @interface Test{
    
    }

3、@Documented

@Documented 用于指定被该元注解修饰的注解会被 javadoc 工具提取成文档。示例如下:

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
//定义 Test 注解会被 javadoc 工具提取
@Documented
public @interface Test{
    
    }

4、@Inherited

一个被 @Inherited 注解了的注解修饰了一个父类,如果他的子类没有被其他注解修饰,则它的子类也继承了父类的注解。示例如下:

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
public @interface Test{
    
    }

@Test
class Base{
    
    }
//Inheritable 类没有任何注解,因此会继承父类的 @Test 注解
public class Inheritable extends Base{
    
    }

5、@Repeatable

@Repeatable 用于指定一个注解可以同时取多个值。示例如下:

@Repeatable(Persons.class)
@interface Person{
    
    
    String name default "";
}

@interface Persons {
    
    
    Person[] value();
}

@Person(role="a")
@Person(role="b")
@Person(role="c")
public class Man{
    
    }
//一个人拥有多个名字

四、Java提供的五个现成注解

1、@Override

@Override 用于指定方法重写,强制一个子类必须覆盖父类的方法。示例如下:

public class Fruit{
    
    
	public void info(){
    
    
		System.out.printIn("水果的 info 方法");
	}
}
class Apple extends Fruit{
    
    
	//使用 @Override 指定下面方法必须重写父类方法
	@Override
	public void info(){
    
    
		System.out.printIn("苹果重写水果的 info 方法");
	}
}

2、@Deprecated

@Deprecated 用于表示某个程序元素(类、方法等)已过时,当其他程序使用已过时的类、方法时,编译器将会给出警告。示例如下:

class Apple{
    
    
	//定义 info 方法已过时
	@Deprecated
	public void info(){
    
    
		System.out.printIn("苹果中的 info 方法");
	}
}
public class DeprecatedTest{
    
    
	public static void main(String[] args){
    
    
		//下面使用 info() 方法时将会被编译器警告
		new Apple().info();
	}
}

3、@SuppressWarnings

@SuppressWarnings 用于取消编译器发出警告。示例如下:

//关闭整个类里的编译器警告
@SuppressWarning("unchecked")
public class SuppressWarningTest{
    
    
	public static void main(String[] args){
    
    
		//此时编译器不会发出警告
		List<String> myList = new ArrayList();
	}
}

调用被 @Deprecated 注解的方法后,编译器会警告提醒,可以用 @SuppressWarnings 来取消这种警告,示例如下:

class Apple{
    
    
	//定义 info 方法已过时
	@Deprecated
	public void info(){
    
    
		System.out.printIn("苹果中的 info 方法");
	}
}
@SuppressWarnings("deprecation")
public class DeprecatedTest{
    
    
	public static void main(String[] args){
    
    
		//下面使用 info() 方法时不会被编译器警告
		new Apple().info();
	}
}

4、@SafeVarargs

@SafeVarargs 是参数安全类型注解。它的目的是提醒开发者不要用参数做一些不安全的操作,它的存在会阻止编译器产生 unchecked 这样的警告,作用与 @SuppressWarning(“unchecked”) 一样。示例如下:

//关闭整个类里的编译器警告
@SafeVarargs
public class SuppressWarningTest{
    
    
	public static void main(String[] args){
    
    
		//此时编译器不会发出警告
		List<String> myList = new ArrayList();
	}
}

5、@FunctionalInterface

如果接口中只有一个抽象方法(可以包含多个默认方法或多个静态方法),该接口就是函数式接口(可以用 Lambda 表达式创建函数式接口的实例)。@FunctionalInterface用于指定某个接口必须是函数式接口。示例如下:

@FunctionalInterface
public interface FunInterface{
    
    
	static void foo(){
    
    
		System.out.printIn("foo类方法");
	}
	default void bar(){
    
    
		System.out.printIn("bar默认方法");
	}
	//有且只能有一个抽象方法
	void test();
}

五、提取注解的信息

当开发者使用了注解修饰类、方法、成员变量等成员之后,这些注解不会自己生效,必须由开发者提供相应的代码来提取并处理注解信息。提取注解的信息主要通过反射实现。程序可以通过以下方法来访问注解信息:

  • boolean isAnnotationPresent(Class<? extends Annotation> annotationClass):判断该程序元素上是否存在指定类型的注解。
  • Annotation[] getAnnotations():返回该程序元素上存在的所有注解。
  • < A extends Annotation> A getAnnotation(Class< A > annotationClass):获取该程序上指定类型的注解,如果该类型的注解不存在,则返回 null。
  • < A extends Annotation> A[] getAnnotationsByType(Class< A > annotationClass):获取该程序上指定类型的多个注解(由重复注解功能带来的),如果该类型的注解不存在,则返回 null。
  • Annotation[] getDeclaredAnnotations():返回该程序元素上存在的所有注解。
  • < A extends Annotation> A getDeclaredAnnotation(Class< A > annotationClass):获取直接修饰该程序元素、指定类型的注解,如果该类型的注解不存在,则返回 null。
  • < A extends Annotation> A[] getDeclaredAnnotationsByType(Class< A > annotationClass):获取直接修饰该程序元素、指定类型的多个注解(由重复注解功能带来的),如果该类型的注解不存在,则返回 null。

获取某个类的注解信息的示例如下:

//定义一个成员变量有默认值的注解
@Retention(RetentionPolicy.RUNTIME)
public @interface RTest{
    
    
	String name() default "qingyunhuohuo";
	int age() default 21;
}

@RTest
public class Test {
    
    
    public static void main(String[] args) {
    
    
        boolean hasAnnotation = Test.class.isAnnotationPresent(RTest.class);
        if ( hasAnnotation ) {
    
    
            RTest rTest = Test.class.getAnnotation(RTest.class);
            System.out.println("name:" + rTest.id());
            System.out.println("age:" + rTest.msg());
        }
    }
}
/*运行程序会输出以下结果
name:qingyunhuohuo
age:21
*/

获取类上的某个方法的注解示例如下:

//定义一个成员变量有默认值的注解
@Retention(RetentionPolicy.RUNTIME)
public @interface RTest{
    
    
	String name() default "qingyunhuohuo";
	int age() default 21;
}

public class Test {
    
    
	@RTest
	@Deprecated
	public void info(){
    
    
		System.out.printIn("info的信息");
	}
	
    public static void main(String[] args) {
    
    
        //获取 Test 类的 info 方法的所有注解
        Annotation[] aArray = Class.forName("Test").getMethod("info").getAnnotations();
        //遍历所有注解
        for (Annotation an : aArray){
    
    
        	System.out.printIn(an);
        }
    }
}

六、注解使用实例

首先写一个注解用于标记哪些方法是可测试的。

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface Testablb{
    
    }

MyTest 测试用例中定义了 8 个方法,这 8 个方法没有太大区别,其中 4 个方法使用 @Testable 注解来标记这些方法是可测试的。

public class MyTest{
    
    
	//使用 @Testable 注解标记该方法是可测试的
	@Testable
	public static void m1(){
    
    }
	public static void m2(){
    
    }

	//使用 @Testable 注解标记该方法是可测试的
	@Testable
	public static void m3(){
    
    
		throw new IllegalArgumentExpection("参数出错了");
	}
	public static void m4(){
    
    
		throw new IllegalArgumentExpection("参数出错了");
	}

	//使用 @Testable 注解标记该方法是可测试的
	@Testable
	public static void m5(){
    
    }
	public static void m6(){
    
    }

	//使用 @Testable 注解标记该方法是可测试的
	@Testable
	public static void m7(){
    
    
		throw new RuntimeExpection("程序业务出现异常");
	}
	public static void m8(){
    
    
		throw new RuntimeExpection("程序业务出现异常");
	}
}

仅仅使用注解来标记程序元素对程序不会有任何影响,这是 Java 注解的一条重要原则。为了让程序中的注解起作用,下面为这些注解提供了一个注解处理工具,该注解处理工具会分析目标类,如果目标类中的方法使用了 @Testable 注解修饰,则通过反射来运行该测试方法。

public class ProcessorTest{
    
    
	public static void process(String clazz) throws ClassNotFoundExpection{
    
    
		int passed = 0;
		int failed = 0;
		//遍历 clazz 对应的类里的所有方法
		for(Method m : Class.forName(clazz).getMethods()){
    
    
			//如果该方法使用了 @Testable 修饰
			if(m.isAnnotationPresent(Testable.class)){
    
    
				try{
    
    
					//调用 m 方法
					m.invoke(null);
					//测试成功,passed 计数器加一
					passed++;
				}
				catch{
    
    
					System.out.printIn("方法" + m + "运行失败,异常" + ex.getCause());
					//测试出现异常,failed 计数器加一
					failed++;
				}
			}
		}
		//统计测试结果
		System.out.printIn("共运行了" + (passed + failed) + "个方法,其中:\n" + "失败了:" + failed + "个,\n" + "成功了:" + passed + "个!");
	}
}

在主程序中,使用 ProcessTest 来分析目标类即可

public class RunTests{
    
    
	public static void main(String[] args) throws Exception{
    
    
		//处理 MyTest 类
		ProcessTest.process("MyTest");
	}
}

运行上面程序,可以看到以下运行结果:

方法public static void MyTest.m3()运行失败,异常:java.lang.IllegalArgumentExpection:参数出错了
方法public static void MyTest.m7()运行失败,异常:java.lang.RuntimeExpection:程序业务出现异常
共运行了:4个方法,其中:
失败了:2个,
成功了:2个!

从通行结果可以看出,程序中的 @Testable 起作用了,MyTest 类中以 @Testable 注解修饰的方法都被测试了。

Guess you like

Origin blog.csdn.net/qingyunhuohuo1/article/details/109197085
Recommended