摘要:从开始,增加了对元数据的支持,也就是注释。该注解只能够修饰接口,不能够修饰其他程序元素。程序处代码使用类来处理本程序中的注解,该处理器分析目标对象中的所有成员变量,如果该成员变量签使用了修饰,则取出
从JDK 5开始,Java增加了对元数据(MetaData)的支持,也就是Annotation(注释)。Annotation提供了一种为程序元素设置元数据的方法,从某些方面来看,Annotation就想修饰符一样,可用于修饰包、类、构造器、方法、成员变量、参数、局部变量的声明,这些信息被存储在Annotation的”name = value”对中
Annotation是一个接口,程序可以通过反射来获取指定程序元素的Annotation对象,然后通过Annotation对象来取得注释里的元数据
Annotation能被用来为程序元素(类、方法、成员变量等)设置元数据,且不会影响程序代码的执行,无论增加、删除Annotation,代码都始终如一地执行。如果希望让程序中的Annotation在运行时起一定的作用,只有通过某种配套的工具对Annotation中的信息进行访问和处理,访问和处理Annotation的工具统称为APT(Annotation Processing Tool)
基本Annotation使用Annotation时要在其前面增加@符号,并把该Annotation当成一个修饰符使用,用于修饰它支持的程序元素
5个基本的Annotation
@Override
@Deprecated
@SuppressWarnings
@SafeVarargs
@FunctionalInterface
限定重写父类方法:@Override@Override 就是用来指定方法覆载,它可以强制一个子类必须覆盖父类的方法。@Override的作用是告诉编译器检查这个方法,保证父类要包含一个被该方法重写的方法,否则就会编译出错。@Override主要是帮助程序员避免一些低级错误,如重写info()方法,却手误写成了inf(),编译器不会报错,你可能找半天才能找到错误
@Override 只能修饰方法,不能修饰其他程序元素
标示已过时:@Deprecated@Deprecated 用于表示某个程序元素(类,方法等)已过时,当其他程序使用已过时的类,方法时,编译器将会给出警告
抑制编译器警告:@SuppressWarnings@SuppressWarnings 指示被该Annotation修饰的程序元素(以及该程序元素中的所有子元素)取消显示指定的编译器警告。@SuppressWarnings 会一直作用域该程序元素的所有子元素,例如,使用@SuppressWarnings修饰某个类取消显示某个编译器警告,同时又修饰该类里的某个方法取消显示另一个编译器警告,那么该方法将会同时取消显示这两个编译器警告
Java 7的“堆污染”警告与@SafeVarargsList list = new ArrayList(); list.add(10); //添加元素时引发unchecked异常 // 下面代码引起“未经检查的转换”的警告,但编译、运行时完全正常 List temp = list; // ① // 但只要访问temp里的元素,就会引起运行时异常 System.out.println(temp.get(0));
“堆污染”(Heap pollution):当把一个不带泛型的对象赋给一个带泛型的变量时,往往就会方式这种“堆污染”
Java会在定义该方法时就发出“堆污染”警告,这样保证开发者“更早”地注意到程序中可能存在的“漏洞”。有些时候,开发者不希望看到这个警告,则可以使用如下三种方式来“抑制”这个警告
使用@SafeVarargs 修饰引发该警告的方法或构造器
使用@SuppressWarnings("unchecked")修饰
编译时使用-Xlint:varargs选项(很少使用)
Java 8的函数式接口与@FunctionalInterfaceJava 8规定:如果接口中只有一个抽象方法(可以包含多个默认方法或多个static方法),该接口就是函数式接口。该注解只能够修饰接口,不能够修饰其他程序元素。@FunctionalInterface 只是告诉编译器检查这个接口,保证该接口只能包含一个抽象方法,否则就会编译出错
@FunctionalInterface 只能修饰接口,不能修饰其他程序元素
JDK的元AnnotationJDK除了在java.lang下提供了5个基本的Annotation之外,还在java.lang.annotation包下提供了6个Meta Annotation,其中有5个元Annotation都用于修饰其他的Annotation定义。其中@Repeatable专门用于定义Java 8新增的重复注解
使用@Retention@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
@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
@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
@Inherited 元Annotation指定被它修饰的Annotation将具有继承性——如果某个类使用了@Xxx注解(定义该Annotation时使用了@Inherited修饰)修饰,则其子类将自动被@Xxx修饰
自定义Annotation 定义Annotation定义一个新的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不会自己生效,必须由开发者提供相应的工具来提取并处理Annotation信息
AnnotatedElement接口是所有程序元素(如Class、Method、Constructor等)的父接口,所以程序通过反射获取了某个类的AnnotatedElement对象(如Class、Method、Constructor等)之后,程序就可以调用该对象的如下几个方法来访问Annotation信息
Annotation[] getAnnotations():返回该程序元素上存在的所有注解,若没有注解,返回长度为0的数组
Annotation[] getDeclaredAnnotations():返回直接修饰该程序元素的所有Annotation
boolean isAnnotationPresent(Class annotationClass) :判断该程序元素上是否包含指定类型的注解,存在则返回true,否则返回false
// 获取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的示例 e.g. One
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) + "个方法,其中: " + "失败了:" + failed + "个, " + "成功了:" + passed + "个!"); } }
public class RunTests { public static void main(String[] args) throws Exception { // 处理MyTest类 ProcessorTest.process("MyTest"); } }e.g. Two
通过使用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新增的重复注解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) { Classclazz = 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新增的Type Annotation
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){} }
文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。
转载请注明本文地址:https://www.ucloud.cn/yun/66530.html
摘要:定义注解,也叫元数据。它可以声明在包类字段方法局部变量方法参数等的前面,用来对这些元素进行说明,注释。方法返回该程序元素上存在的所有注解。与此接口中的其他方法不同,该方法将忽略继承的注释。 定义: 注解(Annotation),也叫元数据。一种代码级别的说明。它是JDK5.0及以后版本引入的一个特性,与类、接口、枚举是在同一个层次。 它可以声明在包、类、字段、方法、局部变量、方法参数等...
摘要:注解提供了一种安全的类似注释的机制,用来将任何的信息或元数据与程序元素类方法成员变量等进行关联。为程序的元素类方法成员变量加上更直观更明了的说明,这些说明与程序的业务逻辑无关,并且提供给指定的工具或框架使用。 什么是注解? Annotation 是 Java5 之后开始引入的新特性,中文为注解。注解提供了一种安全的类似注释的机制,用来将任何的信息或元数据(metadata)与程序元素(...
摘要:的在日常开发,特别是开发中使用广泛,各种框架,测试框架多多少少都会引入一些注解。的功能是作用于程序元数据的特殊类型。而在编译期使用注解则需要特殊的工具,本文不讨论。可以看到通过类中的反射方法获取到了这个类的注解以及其方法的注解。 Java的Annotation在日常开发,特别是java web开发中使用广泛,各种web框架,测试框架多多少少都会引入一些注解。若对java注解有一个全面深...
摘要:另一个很重要的因素是定义了一种标准的描述元数据的方式。对于注解,它的用户就是虚拟机,工作在字节码层面,在编译阶段进行检查,其处理机制主要是内部处理。 什么是注解 用一个词就可以描述注解,那就是元数据,即一种描述数据的数据。所以,可以说注解就是源代码的元数据。比如,下面这段代码: @Override public String toString() { return This is St...
阅读 602·2021-11-22 15:32
阅读 2703·2021-11-19 09:40
阅读 2264·2021-11-17 09:33
阅读 1236·2021-11-15 11:36
阅读 1838·2021-10-11 10:59
阅读 1448·2019-08-29 16:41
阅读 1720·2019-08-29 13:45
阅读 2117·2019-08-26 13:36