注解类型
1.@Retention(RetentionPolicy.RUNTIME)元注解表明,Test注解应该在运行时保留。如果没有保留,测试工具就无法知道Test注解。public enum RetentionPolicy,此枚举类型的常量描述了用于保留注释的各种策略。它们与Retention
元注释类型结合使用,以指定要保留注释的时间。
CLASS | 注解只被保留到编译进行的时候,它并不会被加载到 JVM 中。这是默认行为。 |
RUNTIME | 注解可以保留到程序运行的时候,它会被加载进入到 JVM 中,所以在程序运行时可以获取到它们。因此可以反射性地读取它们。 |
SOURCE | 注解只在源码阶段保留,在编译器进行编译时它将被丢弃忽视。 |
2.@Target(ElementType.METHOD)元注解表明,Test注解只在方法声明中才合法:它不能运用到类声明、域声明或者其他程序元素上。
ANNOTATION_TYPE | 可以给一个注解进行注解 |
CONSTRUCTOR | 构造函数注解 |
FIELD | 字段属性注解(包括枚举常量) |
LOCAL_VARIABLE | 局部变量注解 |
METHOD | 方法注解 |
PACKAGE | 可以给一个包进行注解 |
PARAMETER | 正式参数注解 |
TYPE | 类,接口(包括注释类型)或枚举注解 |
TYPE_PARAMETER | 可以给一个方法内的参数进行注解 |
TYPE_USE | 使用一种类型 |
3.@Documented顾名思义,这个元注解肯定是和文档有关。它的作用是能够将注解中的元素包含到 Javadoc 中去。
4.@Inherited 是继承的意思,但是它并不是说注解本身可以继承,而是说如果一个超类被 @Inherited 注解过的注解进行注解的话,那么如果它的子类没有被任何注解应用的话,那么这个子类就继承了超类的注解。 写个例子讲解一下:
@Inherited
@Retention(RetentionPolicy.RUNTIME)
@interface Test {}
@Test
public class A {}
public class B extends A {}
可以这么理解,注解 Test 被 @Inherited 修饰,之后类 A 被 Test 注解,类 B 继承 A,类 B 也拥有 Test 这个注解。
5.@Repeatable 自然是可重复的意思。@Repeatable 是 Java 1.8 才加进来的,所以算是一个新的特性。什么时候注解会多次使用呢?这里我们看一个列子,一个人他既是程序员又是产品经理,同时他还是个画家。
@interface Persons {
Person[] value();
}
@Repeatable(Persons.class)
@interface Person{
String role default "";
}
@Person(role="artist")
@Person(role="coder")
@Person(role="PM")
public class SuperMan{
}
注意上面的代码,@Repeatable 注解了 Person。而 @Repeatable 后面括号中的类相当于一个容器注解。什么是容器注解呢?就是用来存放其它注解的地方。它本身也是一个注解。我们再看看代码中的相关容器注解。
@interface Persons {
Person[] value();
}
按照规定,它里面必须要有一个 value 的属性,属性类型是一个被 @Repeatable 注解过的注解数组,注意它是数组。如果不好理解的话,可以这样理解。Persons 是一张总的标签,上面贴满了 Person 这种同类型但内容不一样的标签。把 Persons 给一个 SuperMan 贴上,相当于同时给他贴了程序员、产品经理、画家的标签。我们可能对于 @Person(role=”PM”) 括号里面的内容感兴趣,它其实就是给 Person 这个注解的 role 属性赋值为 PM ,大家不明白正常,马上就讲到注解的属性这一块。
6.@Native,指示可以从本机代码引用定义常量值的字段。注释可以用作生成本机头文件的工具的提示,以确定是否需要头文件,如果需要,它应该包含哪些声明。
注解的属性
1.注解的属性也叫做成员变量。注解只有成员变量,没有方法。注解的成员变量在注解的定义中以“无形参的方法”形式来声明,其方法名定义了该成员变量的名字,其返回值定义了该成员变量的类型。
2.需要注意的是,在注解中定义属性时它的类型必须是 8 种基本数据类型外加 类、接口、注解及它们的数组。
3.注解中属性可以有默认值,默认值需要用 default 关键值指定
下面来看一个列子:
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface Test {
public int age() default 25;
public String name() default "zhangsan";
}
Test 中 age 属性默认值为 25,name 属性默认值为 zhangsan,所以在使用注释的时候可以省略掉括号的值。
@Test()
public class Sample {}
java预置的注解
1.@Deprecated这个元素是用来标记过时的元素,想必大家在日常开发中经常碰到。编译器在编译阶段遇到这个注解时会发出提醒警告,告诉开发者正在调用一个过时的元素比如过时的方法、过时的类、过时的成员变量。
2.@Override这个大家应该很熟悉了,提示子类要复写父类中被 @Override 修饰的方法
3.@SuppressWarnings阻止警告的意思。之前说过调用被 @Deprecated 注解的方法后,编译器会警告提醒,而有时候开发者会忽略这种警告,他们可以在调用的地方通过 @SuppressWarnings 达到目的。
4.@SafeVarargs参数安全类型注解。它的目的是提醒开发者不要用参数做一些不安全的操作,它的存在会阻止编译器产生 unchecked 这样的警告。它是在 Java 1.7 的版本中加入的。
5.@FunctionalInterface函数式接口注解,这个是 Java 1.8 版本引入的新特性。函数式编程很火,所以 Java 8 也及时添加了这个特性。想了解更多的同学可以自行查找资料。
为了更好的理解注解,首先来看一个注解的例子:
/**
* 只用于无参的静态方法
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface Test {
}
Test注解类型的声明就是它自身通过Retention和Target注解进行了注解。注解类型声明中的这种注解被称为元注解(meta-annotation)。
上面例子中的这个注解只能用于无参的静态方法。如果编译器能够强制这一限制最好,但是它做不到。编译器可以替你完成多少错误检查,这个是有限制的,即使利用注解。下面就是现实应用中的Test注解,称作标记注解,因为它没有参数,只是“标注”被注解的元素。
public class Sample {
@Test public static void m1(){};
public static void m2(){};
@Test public static void m3(){
throw new RuntimeException("Boom");
};
public static void m4(){};
@Test public void m5(){};
public static void m6(){};
@Test public static void m7(){
throw new RuntimeException("crash");
};
public static void m8(){};
}
Test注解对Sample类的语义没有直接的影响。他们只负责提供信息供相关的程序使用。更一般来讲,注解永远不会改变被注解代码的语义。下面来看一下简单的测试运行类:
public class RunTests {
public static void main(String[] args) throws ClassNotFoundException {
int test = 0;
int passed = 0;
Class<?> aClass = Class.forName("cn.modesty.effective.annotation.Sample");
for (Method m : aClass.getDeclaredMethods()) {
if (m.isAnnotationPresent(Test.class)){
test++;
try {
m.invoke(null);
passed++;
}catch (InvocationTargetException wrappedExc){
Throwable exc = wrappedExc.getCause();
System.out.println(m+" failed "+exc);
}catch (Exception exc){
System.out.println("INVALID @Test:"+ m);
}
}
}
System.out.printf("passed : %d,Failed :%d%n",passed,test -passed);
}
}
来看一下打印的结果:
public static void cn.modesty.effective.annotation.Sample.m7() failed java.lang.RuntimeException: crash
public static void cn.modesty.effective.annotation.Sample.m3() failed java.lang.RuntimeException: Boom
INVALID @Test:public void cn.modesty.effective.annotation.Sample.m5()
passed : 1,Failed :3
Method.invoke反射式地运行类中所有标注了Test的方法。isAnnotationPresent方法告知该工具要运行哪些方法。