java annotation 必须_Java 注释 Annotation

从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

@SuppressWarnings

@SafeVarargs

@FunctionalInterface

限定重写父类方法:@Override

@Override 就是用来指定方法覆载,它可以强制一个子类必须覆盖父类的方法。@Override的作用是告诉编译器检查这个方法,保证父类要包含一个被该方法重写的方法,否则就会编译出错。@Override主要是帮助程序员避免一些低级错误,如重写info()方法,却手误写成了inf(),编译器不会报错,你可能找半天才能找到错误

@Override 只能修饰方法,不能修饰其他程序元素

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

抑制编译器警告:@SuppressWarnings

@SuppressWarnings 指示被该Annotation修饰的程序元素(以及该程序元素中的所有子元素)取消显示指定的编译器警告。@SuppressWarnings 会一直作用域该程序元素的所有子元素,例如,使用@SuppressWarnings修饰某个类取消显示某个编译器警告,同时又修饰该类里的某个方法取消显示另一个编译器警告,那么该方法将会同时取消显示这两个编译器警告

Java 7的“堆污染”警告与@SafeVarargs

List 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的函数式接口与@FunctionalInterface

Java 8规定:如果接口中只有一个抽象方法(可以包含多个默认方法或多个static方法),该接口就是函数式接口。该注解只能够修饰接口,不能够修饰其他程序元素。@FunctionalInterface 只是告诉编译器检查这个接口,保证该接口只能包含一个抽象方法,否则就会编译出错

@FunctionalInterface 只能修饰接口,不能修饰其他程序元素

JDK的元Annotation

JDK除了在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信息

T getAnnotation(Class annotationClass):返回该程序元素上存在的、指定类型的注解,如果该类型的注解不存在,则返回null

T getDeclaredAnnotation(Class annotationClass):该方法尝试获取直接修饰该程序元素、指定类型的Annotation。如果该类型的注解不存在,则返回null

Annotation[] getAnnotations():返回该程序元素上存在的所有注解,若没有注解,返回长度为0的数组

Annotation[] getDeclaredAnnotations():返回直接修饰该程序元素的所有Annotation

boolean isAnnotationPresent(Class<?extends Annotation> annotationClass) :判断该程序元素上是否包含指定类型的注解,存在则返回true,否则返回false

T[] getAnnotationsByType(Class annotationClass):该方法的功能与getAnnotation()方法基本相似。使用该方法获取修饰该元素、指定类型的多个Annotation

T[] getDeclaredAnnotationsByType(Class 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的示例

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)

+ "个方法,其中:\n" + "失败了:" + failed + "个,\n"

+ "成功了:" + 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)

{

Class 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新增的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 info){}

}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值