从JDK 5开始,Java增长了对元数据(MetaData)的支持,也就是Annotation(注释)。Annotation提供了一种为程序元素设置元数据的方法,从某些方面来看,Annotation就想修饰符同样,可用于修饰包、类、构造器、方法、成员变量、参数、局部变量的声明,这些信息被存储在Annotation的”name = value”对中java
Annotation是一个接口,程序能够经过反射来获取指定程序元素的Annotation对象,而后经过Annotation对象来取得注释里的元数据程序员
Annotation能被用来为程序元素(类、方法、成员变量等)设置元数据,且不会影响程序代码的执行,不管增长、删除Annotation,代码都始终如一地执行。若是但愿让程序中的Annotation在运行时起必定的做用,只有经过某种配套的工具对Annotation中的信息进行访问和处理,访问和处理Annotation的工具统称为APT(Annotation Processing Tool)编程
使用Annotation时要在其前面增长@符号,并把该Annotation当成一个修饰符使用,用于修饰它支持的程序元素数组
5个基本的Annotation框架
@Overridejsp
@Deprecatedide
@SuppressWarnings函数
@SafeVarargs工具
@FunctionalInterface测试
@Override 就是用来指定方法覆载,它能够强制一个子类必须覆盖父类的方法。@Override的做用是告诉编译器检查这个方法,保证父类要包含一个被该方法重写的方法,不然就会编译出错。@Override主要是帮助程序员避免一些低级错误,如重写info()方法,却手误写成了inf(),编译器不会报错,你可能找半天才能找到错误
@Override 只能修饰方法,不能修饰其余程序元素
@Deprecated 用于表示某个程序元素(类,方法等)已过期,当其余程序使用已过期的类,方法时,编译器将会给出警告
@SuppressWarnings 指示被该Annotation修饰的程序元素(以及该程序元素中的全部子元素)取消显示指定的编译器警告。@SuppressWarnings 会一直做用域该程序元素的全部子元素,例如,使用@SuppressWarnings修饰某个类取消显示某个编译器警告,同时又修饰该类里的某个方法取消显示另外一个编译器警告,那么该方法将会同时取消显示这两个编译器警告
List list = new ArrayList<Integer>(); list.add(10); //添加元素时引起unchecked异常 // 下面代码引发“未经检查的转换”的警告,但编译、运行时彻底正常 List<String> temp = list; // ① // 但只要访问temp里的元素,就会引发运行时异常 System.out.println(temp.get(0));
“堆污染”(Heap pollution):当把一个不带泛型的对象赋给一个带泛型的变量时,每每就会方式这种“堆污染”
Java会在定义该方法时就发出“堆污染”警告,这样保证开发者“更早”地注意到程序中可能存在的“漏洞”。有些时候,开发者不但愿看到这个警告,则可使用以下三种方式来“抑制”这个警告
使用@SafeVarargs 修饰引起该警告的方法或构造器
使用@SuppressWarnings("unchecked")修饰
编译时使用-Xlint:varargs选项(不多使用)
Java 8规定:若是接口中只有一个抽象方法(能够包含多个默认方法或多个static方法),该接口就是函数式接口。该注解只可以修饰接口,不可以修饰其余程序元素。@FunctionalInterface 只是告诉编译器检查这个接口,保证该接口只能包含一个抽象方法,不然就会编译出错
@FunctionalInterface 只能修饰接口,不能修饰其余程序元素
JDK除了在java.lang下提供了5个基本的Annotation以外,还在java.lang.annotation包下提供了6个Meta Annotation,其中有5个元Annotation都用于修饰其余的Annotation定义。其中@Repeatable专门用于定义Java 8新增的重复注解
@Retention 只能用于修饰Annotation定义,用于指定被修饰的Annotation能够保留多长时间,@Retention包含一个RetentionPolicy类型的value成员变量,因此使用@Retention时候必须为该value成员变量指定值
value成员变量的值只能是以下三个:
RetentionPolicy.CLASS:编译器将把Annotation记录在class文件中。当运行java程序时,JVM不能够获取Annotation信息。这是默认值
RetentionPolicy.RUNTIME:编译器将把Annotation记录在class文件中。当运行java程序时,JVM能够获取Annotation信息,程序也能够经过反射获取该Annotation信息
RetentionPolicy.SOURCE:Annotation只保留在源代码中,编译器直接丢弃这种Annotation
// 定义下面的Testable Annotation保留到运行时 @Retention(value = RetentionPolicy.RUNTIME) public @interface Testable{} // 定义下面的Testable Annotation将被编译器直接丢弃 @Retention(RetentionPolicy.SOURCE) public @interface Testable{}
@Target 也只能用来修饰一个Annotation定义,它用于指定被修饰的Annotation能用于修饰哪些程序单元
其value值有以下几个:
ElementType.ANNOTATION_TYPE:指定该策略的Annotation只能修饰Annotation
ElementType.CONSTRUCTOR:指定该策略的Annotation只能修饰构造器
ElementType.FIELD:指定该策略的Annotation只能修饰成员变量
ElementType.LOCAL_VARIABLE:指定该策略的Annotation只能修饰局部变量
ElementType.METHOD:指定该策略的Annotation只能修饰方法定义
ElementType.PACKAGE:指定该策略的Annotation只能修饰包定义
ElementType.PARAMETER:指定该策略的Annotation能够修饰参数
ElementType.TYPE:指定该策略的Annotation能够修饰类、接口(包括注释类型)或枚举定义
// 指定@ActionListenerFor Annotation只能修饰成员变量 @Target(ElementType.FIELD) public @interface ActionListenerFor{}
@Documented 用于指定被该元Annotation修饰的Annotation类将被javadoc工具提取成文档,若是定义Annotation类时候使用了@Documented 修饰,则全部使用该Annotation修饰的程序元素的API文档中将会包含该Annotation说明
@Retention(RetentionPolicy.RUNTIME) @Target(ElementType.METHOD) // 定义Param Annotation将被javadoc工具提取 @Documented public @interface Param { long id(); String name(); String team() default "Cleveland"; } public class Person { public static void main(String[]args) { ... } // 使用@Param修饰toPerson()方法 @Param(id = 23, name = "James") public void toPerson() { ... } }
@Inherited 元Annotation指定被它修饰的Annotation将具备继承性——若是某个类使用了@Xxx注解(定义该Annotation时使用了@Inherited修饰)修饰,则其子类将自动被@Xxx修饰
定义一个新的Annotation与定义一个接口相似,须要使用@interface关键字,例以下面定义了一个名为Param的Annotation,并在Test类中使用它:
public @interface Param { }
能够在程序的任何地方使用该Annotation,可用于修饰程序中的类、方法、变量、接口等定义。一般会把Annotation放在全部修饰符以前,另放一行
// 使用@Param修饰类定义 @Param public class Test { public static void main(String[]args) { } }
在默认状况下,Annotation可用于修饰任何程序元素,包括类、接口、方法等。如普通方法同样,Annotation还能够带成员变量,Annotation的成员变量在Annotation定义中以无形参的方法形式来声明,其方法名和返回值定义了该成员变量的名字和类型,如:
public @interface Param { long id(); String name(); String team() default "Cleveland"; }
一旦在Annotation里定义了成员变量,使用时就须要为其指定值;也能够为成员变量指定初始值(默认值),指定成员变量的初始值可以使用default关键字,此时能够不为这些成员变量指定值
@Param(id = 2, name = "Irving") public class Animal { public static void main(String[]args) { ... } }
根据Annotation按是否包含成员变量,Annotation分为两类:
标记Annotation:没有定义成员变量的Annotation类型称为标记。这种Annotation仅利用自身的存在与否来为咱们提供信息,例如@Override 、@Deprecated等
元数据Annotation:包含成员变量的Annotation,由于它们能够接受更多的元数据
使用Annotation修饰了类、方法、成员变量等成员后,这些Annotation不会本身生效,必须由开发者提供相应的工具来提取并处理Annotation信息
AnnotatedElement接口是全部程序元素(如Class、Method、Constructor等)的父接口,因此程序经过反射获取了某个类的AnnotatedElement对象(如Class、Method、Constructor等)以后,程序就能够调用该对象的以下几个方法来访问Annotation信息
<T extends Annotation> T getAnnotation(Class<T> annotationClass):返回该程序元素上存在的、指定类型的注解,若是该类型的注解不存在,则返回null
<T extends Annotation> T getDeclaredAnnotation(Class<T> annotationClass):该方法尝试获取直接修饰该程序元素、指定类型的Annotation。若是该类型的注解不存在,则返回null
Annotation[] getAnnotations():返回该程序元素上存在的全部注解,若没有注解,返回长度为0的数组
Annotation[] getDeclaredAnnotations():返回直接修饰该程序元素的全部Annotation
boolean isAnnotationPresent(Class<?extends Annotation> annotationClass) :判断该程序元素上是否包含指定类型的注解,存在则返回true,不然返回false
<T extends Annotation> T[] getAnnotationsByType(Class<T> annotationClass):该方法的功能与getAnnotation()方法基本类似。使用该方法获取修饰该元素、指定类型的多个Annotation
<T extends Annotation> T[] getDeclaredAnnotationsByType(Class<T> annotationClass):该方法的功能与getDeclaredAnnotation()方法基本类似。使用该方法获取直接修饰该元素、指定类型的多个Annotation
// 获取Test类的info方法里的全部注解,并将这些注解打印出来 Annotation[] aArray = Class.forName("Test").getMethod("info").getAnnotations(); // 遍历全部注解 for (Annotation an : aArray) { System.out.println(an); }
若是须要获取某个注解里的元数据,则能够将注解强制类型转换成所需的主家楼下,而后经过注解对象的抽象方法来访问这些元数据
// 获取tt对象的info方法所包含的全部注解 Annotation[] annotation = tt.getClass.forName().getMethod("info").getAnnotations(); // 遍历每一个注解对象 for (Annotation tag : annotation) { // 若是tag注解是MyTag1类型 if ( tag instanceof MyTag1) { System.out.println("Tag is: " + tag); // 将tag强制类型转换伟MyTag1 // 输出tag对象的method1和method2两个成员变量的值 System.out.println("tag.name(): " + ((MyTag1)tag).method1()); System.out.println("tag.age(): " + ((MyTag1)tag).method2()); } // 若是tag注解是MyTag2类型 if ( tag instanceof MyTag2) { System.out.println("Tag is: " + tag); // 将tag强制类型转换伟MyTag2 // 输出tag对象的method1和method2两个成员变量的值 System.out.println("tag.name(): " + ((MyTag2)tag).method1()); System.out.println("tag.age(): " + ((MyTag2)tag).method2()); } }
Annotation
Testable没有任何成员变量,仅是一个标记Annotation,做用是标记哪些方法是可测试的。程序经过判断该Annotation存在与否来决定是否运行指定方法
import java.lang.annotation.*; // 使用JDK的元数据Annotation:Retention @Retention(RetentionPolicy.RUNTIME) // 使用JDK的元数据Annotation:Target @Target(ElementType.METHOD) // 定义一个标记注解,不包含任何成员变量,即不可传入元数据 public @interface Testable { }
@Testable 用于标记哪些方法是可测试的,该Annotation能够做为JUnit测试框架的补充。在JUnit框架中,测试用例的测试方法必须以test开头。若是使用@Testable 注解,则可把任何方法标记为可测试的
public class MyTest { // 使用@Testable注解指定该方法是可测试的 @Testable public static void m1() { } public static void m2() { } // 使用@Testable注解指定该方法是可测试的 @Testable public static void m3() { throw new IllegalArgumentException("参数出错了!"); } public static void m4() { } // 使用@Testable注解指定该方法是可测试的 @Testable public static void m5() { } public static void m6() { } // 使用@Testable注解指定该方法是可测试的 @Testable public static void m7() { throw new RuntimeException("程序业务出现异常!"); } public static void m8() { } }
注解处理工具分析目标类,若是目标类中的方法使用了@Testable 注解修饰,则经过反射来运行该测试方法
import java.lang.reflect.*; public class ProcessorTest { public static void process(String clazz) throws ClassNotFoundException { 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计数器加1 passed++; } catch (Exception ex) { System.out.println("方法" + m + "运行失败,异常:" + ex.getCause()); // 测试出现异常,failed计数器加1 failed++; } } } // 统计测试结果 System.out.println("共运行了:" + (passed + failed) + "个方法,其中:\n" + "失败了:" + failed + "个,\n" + "成功了:" + passed + "个!"); } }
public class RunTests { public static void main(String[] args) throws Exception { // 处理MyTest类 ProcessorTest.process("MyTest"); } }
经过使用Annotation来简化事件编程,在传统的事件编程中老是须要经过addActionListener()方法来为事件源绑定事件监听器,下面的示例经过@ActionListenerFor来为程序中的按钮绑定事件监听器
import java.lang.annotation.*; import java.awt.event.*; @Target(ElementType.FIELD) @Retention(RetentionPolicy.RUNTIME) public @interface ActionListenerFor { // 定义一个成员变量,用于设置元数据 // 该listener成员变量用于保存监听器实现类 Class<? extends ActionListener> listener(); }
使用@ActionListenerFor 注解来为两个按钮绑定事件监听器
import java.awt.event.*; import javax.swing.*; public class AnnotationTest { private JFrame mainWin = new JFrame("使用注解绑定事件监听器"); // 使用Annotation为ok按钮绑定事件监听器 @ActionListenerFor(listener=OkListener.class) private JButton ok = new JButton("肯定"); // 使用Annotation为cancel按钮绑定事件监听器 @ActionListenerFor(listener=CancelListener.class) private JButton cancel = new JButton("取消"); public void init() { // 初始化界面的方法 JPanel jp = new JPanel(); jp.add(ok); jp.add(cancel); mainWin.add(jp); ActionListenerInstaller.processAnnotations(this); // ① mainWin.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); mainWin.pack(); mainWin.setVisible(true); } public static void main(String[] args) { new AnnotationTest().init(); } } // 定义ok按钮的事件监听器实现类 class OkListener implements ActionListener { public void actionPerformed(ActionEvent evt) { JOptionPane.showMessageDialog(null , "单击了确认按钮"); } } // 定义cancel按钮的事件监听器实现类 class CancelListener implements ActionListener { public void actionPerformed(ActionEvent evt) { JOptionPane.showMessageDialog(null , "单击了取消按钮"); } }
定义了两个JButton按钮,并使用@ActionListenerFor 注解为这两个按钮绑定了事件监听器,使用@ActionListenerFor 注解时传入了listener元数据,该数据用于设定每一个按钮的监听器实现类。程序①处代码使用ActionListenerInstaller类来处理本程序中的注解,该处理器分析目标对象中的全部成员变量,若是该成员变量签使用了@ActionListenerFor修饰,则取出该Annotation中的listener元数据,并根据该数据来绑定事件监听器
import java.lang.reflect.*; import java.awt.event.*; import javax.swing.*; public class ActionListenerInstaller { // 处理Annotation的方法,其中obj是包含Annotation的对象 public static void processAnnotations(Object obj) { try { // 获取obj对象的类 Class cl = obj.getClass(); // 获取指定obj对象的全部成员变量,并遍历每一个成员变量 for (Field f : cl.getDeclaredFields()) { // 将该成员变量设置成可自由访问。 f.setAccessible(true); // 获取该成员变量上ActionListenerFor类型的Annotation ActionListenerFor a = f.getAnnotation(ActionListenerFor.class); // 获取成员变量f的值 Object fObj = f.get(obj); // 若是f是AbstractButton的实例,且a不为null if (a != null && fObj != null && fObj instanceof AbstractButton) { // 获取a注解里的listner元数据(它是一个监听器类) Class<? extends ActionListener> listenerClazz = a.listener(); // 使用反射来建立listner类的对象 ActionListener al = listenerClazz.newInstance(); AbstractButton ab = (AbstractButton)fObj; // 为ab按钮添加事件监听器 ab.addActionListener(al); } } } catch (Exception e) { e.printStackTrace(); } } }
根据@ActionListenerFor注解的元数据取得了监听器实现类,而后经过反射来建立监听器对象,接下来将监听器对象绑定到指定的按钮(按钮由被@ActionListenerFor修饰的Field表示)
Java8容许使用多个相同类型的Annotation来修饰同一个类
@Result (name = "failure", location = "failed.jsp") @Result (name = "success", location = "succ.jsp") public Acton FooAction{...}
若是定义了@FkTag(无@Repeatable版)注解,该注解包括两个成员变量。但该注解默认不能做为重复注解使用,若是使用两个以上的以下注解修饰同一个类,编译器会报错
开发重复注解须要使用@Repeatable 修饰。使用@Repeatable修饰该注解,使用@Repeatable时必须为value成员变量指定值,该成员变量的值应该是一个“容器”注解——该容器注解能够包含多个@FkTag
import java.lang.annotation.*; @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.TYPE) @Repeatable(FkTags.class) public @interface FkTag { // 为该注解定义2个成员变量 String name() default "NBA球员"; int number(); }
“容器”注解可包含多个@FkTag,所以须要定义以下的“容器”注解
import java.lang.annotation.*; @Retention(RetentionPolicy.RUNTIME) // ① @Target(ElementType.TYPE) public @interface FkTags { // 定义value成员变量,该成员变量可接受多个@FkTag注解 FkTag[] value(); // ② }
代码①指定了@FkTags 注解信息可保留到运行时,这是必需的,由于@FkTag 注解信息须要保留到运行时,若是@FkTags 注解只能保留到源代码级别或类文件,将会致使@FkTags 的保留期小于@FkTag 的保留期,若是程序将多个@FkTag注解放入@FkTags中,若JVM丢弃了@FkTags注解,天然也就丢弃了@FkTag的信息
代码②定义了一个FkTag[]类型的value成员变量,这意味着@FkTags 注解的value成员变量可接受多个@FkTags 注解可做为@FkTag 的容器
“容器”注解的保留期必须必它所包含的注解的保留期更长,不然编译器会报错
传统代码使用该注解
@FkTags({@FkTag(number = 23), @FkTag(name = "Westbrooks", number = 0)})
因为@FkTags是重复注解,所以可直接使用两个@FkTag注解,系统依然将两个@FkTag注解做为@FkTags的values成员变量的数组元素
@FkTag(number = 23) @FkTag(name = "Westbrooks", number = 0)
重复注解是一种简化写法,这种简化写法是一种假象:多个重复注解会被做为“容器”注解的value成员变量的数组元素
@FkTag(number = 23) @FkTag(name = "Westbrooks", number = 0) public class FkTagTest { public static void main(String[] args) { Class<FkTagTest> clazz = FkTagTest.class; /* 使用Java 8新增的getDeclaredAnnotationsByType()方法获取 修饰FkTagTest类的多个@FkTag注解 */ FkTag[] tags = clazz.getDeclaredAnnotationsByType(FkTag.class); // 遍历修饰FkTagTest类的多个@FkTag注解 for(FkTag tag : tags) { System.out.println(tag.name() + "-->" + tag.age()); } } }
运行结果:
NBA球员-->23 Westbrooks-->0 @FkTags(value=[@FkTag(name=NBA球员, age=23), @FkTag(name=Westbrooks, age=0)])
Java8为ElementType枚举增长了TYPE_PARAMETER、TYPE_USE两个枚举值,容许定义枚举时使用@Target(ElementType.TYPE_USE)修饰,这种注解称为Type Annotation(类型注解),Type Annotation可用在任何用到类型的地方
容许在以下位置使用Type Annotation
建立对象(用new关键字建立)
类型转换
使用implements实现接口
使用throws声明抛出异常
import java.util.*; import java.io.*; import javax.swing.*; import java.lang.annotation.*; // 定义一个简单的Type Annotation,不带任何成员变量 @Target(ElementType.TYPE_USE) @interface NotNull{} // 定义类时使用Type Annotation @NotNull public class TypeAnnotationTest implements @NotNull /* implements时使用Type Annotation */ Serializable { // 方法形参中使用Type Annotation public static void main(@NotNull String[] args) // throws时使用Type Annotation throws @NotNull FileNotFoundException { Object obj = "fkjava.org"; // 强制类型转换时使用Type Annotation String str = (@NotNull String)obj; // 建立对象时使用Type Annotation Object win = new @NotNull JFrame("俄克拉荷马雷霆"); } // 泛型中使用Type Annotation public void foo(List<@NotNull String> info){} }