文章目录
- 一、基本概念
- 二、注解的定义和使用
- 三、元注解
- 1、@Retention
- 2、@Target
- 3、@Documented
- 4、@Inherited
- 5、@Repeatable
- 四、Java提供的五个现成注解
- 1、@Override
- 2、@Deprecated
- 3、@SuppressWarnings
- 4、@SafeVarargs
- 5、@FunctionalInterface
- 五、提取注解的信息
- 六、注解使用实例
一、基本概念
官方文档对注解(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 注解修饰的方法都被测试了。