Java语言之Annotation基本概述及实例讲解
小标 2018-08-06 来源 : 阅读 948 评论 0

摘要:本文主要向大家介绍了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频道!

本文由 @小标 发布于职坐标。未经许可,禁止转载。
喜欢 | 1 不喜欢 | 0
看完这篇文章有何感觉?已经有1人表态,100%的人喜欢 快给朋友分享吧~
评论(0)
后参与评论

您输入的评论内容中包含违禁敏感词

我知道了

助您圆梦职场 匹配合适岗位
验证码手机号,获得海同独家IT培训资料
选择就业方向:
人工智能物联网
大数据开发/分析
人工智能Python
Java全栈开发
WEB前端+H5

请输入正确的手机号码

请输入正确的验证码

获取验证码

您今天的短信下发次数太多了,明天再试试吧!

提交

我们会在第一时间安排职业规划师联系您!

您也可以联系我们的职业规划师咨询:

小职老师的微信号:z_zhizuobiao
小职老师的微信号:z_zhizuobiao

版权所有 职坐标-一站式IT培训就业服务领导者 沪ICP备13042190号-4
上海海同信息科技有限公司 Copyright ©2015 www.zhizuobiao.com,All Rights Reserved.
 沪公网安备 31011502005948号    

©2015 www.zhizuobiao.com All Rights Reserved

208小时内训课程