Java_Annotation

内容概述

  • Annotation概述
  • 基本Annotation
  • JDK的元Annotation
  • 自定义Annotation
  • 注解分类

一、Annotation概述

        Annotation,其实是代码里的特殊标记,这些标记可以在编译、类加载、运行时被读取,并执行相应的处理。通过使用注解,程序开发人员可以在不改变原有逻辑的情况下,在源文件中嵌入一些补充的信息。代码分析工具、开发工具和部署工具可以通过这些补充信息进行验证或者进行部署。

        Annotation提供了一种为程序元素设置元数据的方法,从某些方面来看,Annotation就像修饰符一样,可用于修饰包、类、构造器、方法、成员变量、参数、局部变量的声明,这些信息被存储在Annotation的“name=value"对中。

        Annotation是一个接口,程序可以通过反射来获取指定程序元素的Annotation对象,然后通过Annotation对象来取得注解里的元数据。如果希望让程序中的Annotation在运行时起一定的作用,只有通过某种配套的工具对Annotation中的信息进行访问和处理,访问和处理Annotation的工具统称APT(Annotation Processing Tool).

二、基本Annotation

       Java提供了5个基本Annotation:@Override,@Deprecated,@SuppressWarnings,@SafeVariargs,@FunctionalInterface.

其中@SafeVarargs是Java7新增的,@FunctionalInterface是Java8新增的。这5个基本的Annotation都定义在java.lang包下。

        1.限定重写父类方法:@Override

        @Override就是用来指定方法重写的,它可以强制一个子类必须复写父类的方法。

public class Fruit
{
	public void info()
	{
		System.out.println("水果的info方法...");
	}
}
class Apple extends Fruit
{
	// 使用@Override指定下面方法必须重写父类方法
	@Override
	public void inf0()
	{
		System.out.println("苹果重写水果的info方法...");
	}
}

PS:@Override主要帮助程序员避免一些低级错误,例如把上面Apple类中的info方法不小心写成了inf0,这样的"低级错误",可能会成为后期排错时的巨大障碍。

        2.标示已过时:@Deprecated

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

class Apple
{
	// 定义info方法已过时
	@Deprecated
	public void info()
	{
		System.out.println("Apple的info方法");
	}
}
public class DeprecatedTest
{
	public static void main(String[] args)
	{
		// 下面使用info方法时将会被编译器警告
		new Apple().info();
	}
}
        3.抑制编译器警告:@SuppressWarnings

        @SuppressWarnings指示被该Annotation修饰的程序元素(以及该程序元素中的所有子元素)取消显示指定的编译器警告。

// 关闭整个类里的编译器警告
@SuppressWarnings(value="unchecked")
public class SuppressWarningsTest
{
	public static void main(String[] args)
	{
		List<String> myList = new ArrayList();     // ①
	}
}

        4.Java7的“堆污染”警告与@SafeVarargs

public class Demo {
	public static void main(String[] args){
		List list = new ArrayList<Integer>();
		list.add(20);//添加元素时引发unchecked异常
		//下面代码引起"未经检查的转换"的警告,编译、运行时完全正常
		List<String> ls = list;//①
		//但只要访问ls里的元素,如下面代码就会引起运行时异常
		System.out.println(ls.get(0));
	}
}

        Java把引发这种错误的原因称为"堆污染"(Heap pollution),当把一个不带泛型的对象赋给一个带泛型的变量时候,往往会发生这种"堆污染",如上①号粗体字代码所示。

        对于形参个数可变的方法,该形参的类型又是泛型,这将更容易导致"堆污染".例如下面工具类:

public class ErrorUtils
{
	@SafeVarargs
	public static void faultyMethod(List<String>... listStrArray)
	{
		// Java语言不允许创建泛型数组,因此listArray只能被当成List[]处理
		// 此时相当于把List<String>赋给了List,已经发生了“擦除”
		List[] listArray = listStrArray;
		List<Integer> myList = new ArrayList<Integer>();
		myList.add(new Random().nextInt(100));
		// 把listArray的第一个元素赋为myList
		listArray[0] = myList;
		String s = listStrArray[0].get(0);
	}
}

        上面程序中的粗体字代码已经发生了"堆污染"。由于该方法有一个形参是List<String>...类型,个数可变的形参相当于数组,但Java又不支持泛型数组,因此程序中只能把List<String>...当成List[]处理,这里就发生了"堆污染"。

        在Java6以及更早的版本中,Java编译器认为faultyMethod()方法完全没有问题,既不会提示错误,也没有提示警告。

public class ErrorUtilsTest
{
	public static void main(String[] args)
	{
		ErrorUtils.faultyMethod(Arrays.asList("Hello!"), Arrays.asList("World!"));
	}
}

        编译该程序将会在①号代码处引发一个unchecked警告。这个unchecked警告出现得比较"突兀":定义faultyMethod()方法时没有任何警告,调用该方法时却引发了一个"警告".

        从Java7开始,Java编译器将会进行更严格的检查,Java编译器在编译ErrorUtils时就会发出一个警告。

        由此可见,Java7会在定义该方法时就发出"堆污染"警告,这样保证开发者"更早"地注意到程序中可能存在的"漏洞"。但在有些时候,开发者不希望看到这个警告,则可以使用如下三种方式来"抑制"这个警告。

        ①:使用@SafeVarargs修饰引发该警告的方法或构造器

        ②:使用@SuppressWarnings("unchecked")修饰

        ③:编译时使用-Xlint:varargs选项

        5.Java8的函数式接口与@FunctionalInterface

        如果接口中只有一个抽象方法(可以包含多个默认方法或多个static方法),该接口就是函数式接口。函数式接口就是为Java8的Lambda表达式准备的,Java8允许使用Lambda表达式创建函数式接口的实例,因此Java8专门增加了@FunctionalInterface.

@FunctionalInterface
public interface FunInterface
{
	static void foo()
	{
		System.out.println("foo类方法");
	}
	default void bar()
	{
		System.out.println("bar默认方法");
	}
	void test(); // 只定义一个抽象方法
}

PS:@FunctionalInterface只是告诉编译器检查这个接口,保证该接口只能包含一个抽象方法,否则就会编译出错。@FunctionalInterface只能修饰接口,不能修饰其他程序元素。

三、JDK的元Annotation

        1.使用@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.

           如果需要通过反射获取注解信息,就需要使用value属性值为RetentionPolicy.RUNTIME的@Retention.使用@Retention元Annotation可采用如下代码为value指定值。

//定义下面的Testable Annotation将被编译器直接丢弃
@Retention(value=RetentionPolicy.SOURCE)
public @interface Testable{}

  or

//定义下面的Testable Annotation保留到运行时
@Retention(value=RetentionPolicy.RUNTIME)
public @interface Testable{}

        如果适用注解时只需要为value成员变量指定值,则使用该注解时可以直接在该注解后的括号里指定value成员变量的值,无须使用"value=变量值"的形式。

        2.使用@Target

        @Target也只能修饰一个Annotation定义,它用于指定被修饰的Annotation能用于修饰哪些程序单元。@Target元Annotation也包含一个名为value的成员变量,该成员变量的值只能是如下几个。

        ElementType.ANNOTATION_TYPE:指定该策略的Annotation只能修饰Annotation.

        ElementType.CONSTRUCTOR:指定该策略的Annotation只能修改构造器。

        ElementType.FIELD:指定该策略的Annotation只能修改成员变量。

        ElementType.LOCAL_VARIABLE:指定该策略的Annotation只能修饰局部变量。

        ElementType.METHOD:指定该策略的Annotation只能修饰方法定义。

        ElementType.PACKAGE:指定该策略的Annotatio只能修饰包定义。

        ElementType.PARAMETER:指定该策略的Annotation可以修饰参数。

        ElementType.TYPE:指定该策略的Annotation可以修饰类、接口(包括注解类型)或枚举定义.

//如下代码指定@ActionListenerFor只能修饰成员变量
@Target(ElementType.FIELD)
public @interface ActionListenerFor{}

        3.使用@Documented

        @Documented用于指定被该元Annotation修饰的Annotation类将被javadoc工具提取成文档,如果定义Annotation类时使用了@Documented修饰,则所有使用该Annotation修饰的程序元素的API文档中将会包含该Annotation说明。

        4.使用@Inherited

        @Inherited元Annotation指定被它修饰的Annotation将具有继承性—如果某个类使用了@Xxx注解(定义该Annotation时使用了@Inherited修饰)修饰,择期子类将自动被@Xxx修饰。

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Inherited
public @interface Inheritable
{
}
// 使用@Inheritable修饰的Base类
@Inheritable
class Base
{
}
// TestInheritable类只是继承了Base类,
// 并未直接使用@Inheritable Annotiation修饰
public class InheritableTest extends Base
{
	public static void main(String[] args)
	{
		// 打印TestInheritable类是否具有@Inheritable修饰
		System.out.println(InheritableTest.class.isAnnotationPresent(Inheritable.class));
	}
}

        上面程序中的Base类使用了@Inheritable修饰,而该Annotation具有继承性,所以其子类也将自动使用@Inheritable修饰。运行上面程序,会看到输出:true.

        5.Java8新增的重复注解

        在Java8以前,同一个程序元素前最多只能使用一个相同类型的Annotation;如果需要在同一个元素前使用多个相同类型的Annotation,则必须使用Annotation"容器"。

// 指定该注解信息会保留到运行时
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface FkTags
{
	// 定义value成员变量,该成员变量可接受多个@FkTag注解
	FkTag[] value();
}
// 指定该注解信息会保留到运行时
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Repeatable(FkTags.class)
public @interface FkTag
{
	// 为该注解定义2个成员变量
	String name() default "疯狂软件";
	int age();
}
@FkTag(age=5)
@FkTag(name="疯狂Java" , age=9)
//@FkTags({@FkTag(age=5),
//	@FkTag(name="疯狂Java" , age=9)})
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());
		}
		/* 使用传统的getDeclaredAnnotation()方法获取
			修饰FkTagTest类的@FkTags注解 */
		FkTags container = clazz.getDeclaredAnnotation(FkTags.class);
		System.out.println(container);
	}
}

PS:重复注解只是一种简化写法,这种简化写法是一种假象:多个重复注解其实会被作为"容器"注解的value成员变量的数组元素。例如上面的重复的@FkTag注解其实会被作为@FkTags注解的value成员变量的数组元素处理。

四、自定义Annotation

           使用Annotation修饰了类、方法、成员变量等成员之后,这些Annotation不会自己生效,必须有开发者提供相应的工具来提供并处理Annotation信息。

            Java使用Annotation接口来代表程序元素前面的注解,该接口是所有注解的父接口。Java5在java.lang.reflect包下新增了AnnotatedElement接口,该接口代表程序中可以接受注解的程序元素。该接口主要有如下几个实现类:Class,Constructor,Field,Method,Package.

                java.lang.reflect包下主要包含一些实现反射功能的工具类,从Java5开始,java.lang.reflect包所提供的反射API增加了读取运行时Annotation的能力。只有当定义Annotation时使用了@Retention(RetentionPolicy.RUNTIME)修饰,该Annotation才会在运行时可见,JVM才会在装载*.class文件时丢保存在class文件中的Annotation.

                因为AnnotatedElement接口是所有程序元素(eg:Class、Method等)的父接口,所以程序通过反射获取了某个类的AnnotatedElement对象(eg:Class、Method等)之后,程序就可以调用该对象的方法来访问Annotation信息。

@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface ActionListenerFor
{
	// 定义一个成员变量,用于设置元数据
	// 该listener成员变量用于保存监听器实现类
	Class<? extends ActionListener> listener();
}
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 , "单击了取消按钮");
	}
}
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();
		}
	}
}

五、注解分类

        1.按照作用域分
根据注解的作用域@Retention,注解分为
RetentionPolicy.SOURCE: Java源文件上的注解
RetentionPolicy.CLASS: Class类文件上的注解
RetentionPolicy.RUNTIME: 运行时的注解
        2.按照来源分
按照注解的来源,也是分为3类
1. 内置注解 如@Override ,@Deprecated 等等
2. 第三方注解,如Hibernate, Struts等等
3. 自定义注解,如仿hibernate的自定义注解
        3.根据Annotation是否可以包含成员变量,可以把Annotation分为如下两类:
1.标记Annotation:没有定义成员变量的Annotation类型被称为标记。
这种Annotation仅利用自身的存在与否来提供信息,eg:@Override等。
2.元数据Annotation:包含成员变量的Annotation,因为它们可以接受更多的元数据,
所以也被称为元数据Annotation.
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值