摘要:本文主要向大家介绍了Java语言之Annotation基本概述及实例讲解,通过具体的内容向大家展示,希望对大家学习JAVA语言有所帮助。
本文主要向大家介绍了Java语言之Annotation基本概述及实例讲解,通过具体的内容向大家展示,希望对大家学习JAVA语言有所帮助。
内容概述
Annotation概述基本AnnotationJDK的元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(); // ①
}
}</string>
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));
}
}
</string></integer>
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);
}
}</integer></integer></string></string>
上面程序中的粗体字代码已经发生了"堆污染"。由于该方法有一个形参是List...类型,个数可变的形参相当于数组,但Java又不支持泛型数组,因此程序中只能把List...当成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);
}
}</fktagtest>
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.
本文由职坐标整理并发布,希望对同学们有所帮助。了解更多详情请关注编程语言JAVA频道!
您输入的评论内容中包含违禁敏感词
我知道了
请输入正确的手机号码
请输入正确的验证码
您今天的短信下发次数太多了,明天再试试吧!
我们会在第一时间安排职业规划师联系您!
您也可以联系我们的职业规划师咨询:
版权所有 职坐标-一站式IT培训就业服务领导者 沪ICP备13042190号-4
上海海同信息科技有限公司 Copyright ©2015 www.zhizuobiao.com,All Rights Reserved.
沪公网安备 31011502005948号