您的位置:首页 > 编程语言 > Java开发

Java —— Annotation(注解)

2016-01-28 16:47 429 查看

Annotation概述

Annotation即注解,也被翻译为注释,是JDK 5 Java对元数据(MetaData)的支持。Annotation是代码里的特殊标记,这些标记可以在编译、类加载、运行时被读取,并执行相应的处理。通过使用注解,程序开发人员可以在不改变逻辑的情况下,在源文件嵌入一些补充的信息。代码分析工具、开发工具和部署工具可以通过这些补充信息进行验证或者进行不熟。

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

Annotation能被用来为程序元素(类、方法、成员变量等)设置元数据。值得指出的是,Annotation不影响程序代码的执行,无论增加、删除Annotation,代码都始终如一地执行。如果希望让程序中的Annotation在运行时起一定的作用,只有通过某种配套的工具对Annotation中的信息进行访问和处理,访问和处理Annotation的工具统称APT(Annotation Processing Tool)。

5个基本Annotation

在系统学习新的Annotation语法之前,下来看一下Java提供的5个基本Annotation的用法。

5个基本的Annotation:

1.@Override

2.@Deprecated

3.@SuppressWarnings

4.@SafeVarargs

5.@FunctionalInterface

上面5个基本Annotation中的@SafeVarargs是Java 7新增的、@FunctionalInterface是Java 8新增的。这5个基本的Annotation都定义在java.lang包下,读者可以通过查阅他们的API文档来了解关于它们的更多细节。

下面我们来逐个解释一下这个5个基本的Annotation。

限定重写父类方法:@Override

@Override就是用来指定一个方法是重写父类中的方法的。例如:

[code]    @Override
    public String toString() {
        return super.toString();
    }


上面是重写Object的toString()方法,我们用@Override来标记这个方法是重写父类Object中的toString()方法。如果不类不存在toString()方法,在编译时就会报错。

@Override主要是帮助程序员避免一些低级错误。例如父类中有个一个execute()的方法,但是我们在重写此方法的时候把方法名字写错了。这样我们在调用这个写错了的方法时不会影响程序的运行,但是得到的结果可能与我们的预期不同。这样的错误排查起来也非常费时费力,有了@Override可一定程度的解决这一问题的发生。

标识已过时:@Deprecated

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

[code]public class Test {

    public static void main(String[] args) {
        // 下面使用info()方法是将会被编译器警告
        new Apple().info();
    }
}

class Apple {

    // 定义info方法已过时
    @Deprecated
    public void info() {
        System.out.println("Apple的info方法");
    }

}


抑制编译器警告:@SuppressWarnings

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

[code]@SuppressWarnings(value="unchecked")
public class Test {

    public static void main(String[] args) {
        List<String> myList = new ArrayList();
    }
}


如上代码所示,new ArrayList();没有带有泛型,所以编译器会有警告,如果想要消除次警告有两种办法,一个是增加泛型,另一个则是使用@SuppressWarnings来标记Test或者标记main方法,也可以直接标记new ArrayList()这一条语句。这样编译器警告就会消失。

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

[code]    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),当把一个不带泛型的对象赋给一个带泛型的变量时,往往就会发生这种“堆污染”,如上面的List< String > ls = list;这句代码。

三种“抑制”这个警告的方法:

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

2.使用@SuppressWarnings(“unchecked”)修饰。

3.编译时使用-Xlint:varargs选项

很明显,第三种方式一般比较少用,通常可以选择第一种或第二种方式,尤其是使用@SafeVarargs修饰引发该警告的方法构造器,它是Java 7专门为抑制“堆污染”警告提供的。

Java 8的函数式接口与@FunctionalInterface

@FunctionalInterface在Lambda博文中已经讲过了。它就是用来指定某个接口必须是函数式接口(一个接口中只能有一个抽象方法),否则编译时就会出错,这是为了防止程序员犯一些第几错误。

JDK的元Annotation

JDK除了在java.lang下提供了5个基本的Annotation之外,还在java.lang.annotation包下提供了6个Meta Annotation(元Annotation),其中有5个元Annotation都用于修饰其他的Annotation定义。其中@Repeatable专门用于定义Java 8新增的重复注解。接下来我们逐个解释。

@Retention

@Retention只能用于修饰Annotation定义,用于指定被修饰的Annotation可以保留多长时间,@Retention包含一个RetentionPolicy类型的value成员变量,所以使用@Retention时必须为该value成员变量指定值。

value成员变量的值只能是如下三个:

1.RetentionPolicy.CLASS:编译器将把Annotation记录在class文件中。当运行Java程序时,JVM不可获取Annotation信息。这是默认值。

2.RetentionPolicy.RUNTIME:编译器将把Annotation记录在class文件中。当运行Java程序时,JVM也可获取Annotation信息。程序可以通过反射获取该Annotation信息。

3.RetentionPolicy.SOURCE:Annotation只能留在源码中,编译器直接丢弃这种Annotation。

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

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

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


如第二种方式所示,当Annotation的变量名为value时,程序中可以直接在Annotation后的括号里指定该成员变量的只,无须使用name=value的形式。

@Target

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

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

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

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

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

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

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

7.ElementType.PARAMETER:指定该策略的Annotation只能修饰参数。

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

与使用@Retention类似的是,使用@Target也可以直接在括号里指定value值,而无须使用name=value的形式。如下代码指定@ActionListenerFor Annotation只能修饰成员变量。

[code]@Target(ElementType.FIELD)
public @interface ActionListenerFor{}


@Documented

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

@Inherited

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

自定义Annotation

我们通过一个完整的例子来演示一下自定义Annotation。代码如下:

[code]@Testable(name="hello",age=20)
public class Test {

    public static void main(String[] args) {

    }
}

// 定义下面的Testable Annotation保留运行时
@Retention(RetentionPolicy.RUNTIME)
// 可以修饰类、接口或枚举
@Target(ElementType.TYPE)
// 可以被javadoc工具提取成文档
@Documented
// 具有继承性
@Inherited
@interface Testable{
    // 定义成员变量name,默认值为Lyong
    String name() default "Lyong";
    // 定义成员变量age,默认值为18
    int age() default 18;
}


上面的例子已经有了详细的注释,我们在来说一下自定义Annotation时一些规则或者注意点。

1.Annotation上的各种修饰之前已经讲解过,我们自定义Annotation时这些是可选的,根据我们的需求去添加不同的Annotation来修饰。

2.声明Annotation与生命接口类似,只不过是将interface关键字之前加了一个@符号。

3.成员变量的声明方式使用类似声明方法一样。

4.成员变量可以有默认值,如果有默认值则使用default关键字后面加默认值,当使用次Annotation时如果为成员变量指定值则默认值无效,否则使用默认值。

根据Annotation是否可以包含成员变量,可以把Annotation分为如下两类。

1.标记Annotation:没有定义成员变量的Annotation类型被称为标记。这种Annotation仅利用自身的存在与否来提供信息,如前面介绍的@Override、@Test等Annotation。

2.元数据Annotation:包含成员变量的Annotation,因为他们可以接受更多的元数据,所以也被称为元数据Annotation。

提取Annotation信息

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

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

1.Class:类定义。

2.Constructor:构造器定义。

3.Field:类的成员变量定义。

4.Method:类的方法定义。

5.Package:类的包定义。

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

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

1.< A extends Annotation > A getAnnotation(Class< A > annotationClass):返回该程序元素上存在的、指定类型的注解,如果该类型的注解不存在,则返回null。

2.< A extends Annotation > A getDeclaredAnnotation(Class< A > annotationClass):这是Java 8新增的方法,该方法尝试获取直接修饰该程序元素、指定类型的Annotation。如果该类型的注解不存在,则返回null。

3.Annotation[] getAnnotations():返回该程序元素上存在的所有注解。

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

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

6.< Aextends Annotation > A[] getAnnotationsByType(Class< A > annotationClass):该方法的功能与前面介绍的getAnnotation()方法基本相似。但由于Java 8增加了重复注解功能,因此需要使用该方法获取修饰该程序元素、指定类型的多个Annotation。

7.< Aextends Annotation > A[] getDeclaredAnnotationsByType(Class< A > annotationClass):该方法的功能与前面介绍的getDeclaredAnnotations()方法基本相似。但由于Java 8增加了重复注解功能,因此需要使用该方法获取直接修饰该程序元素、指定类型的多个Annotation。

接下来我们看一个简单的提取注解信息的例子:

[code]// 使用自定义TestAnnotation注解,name赋值为“arry”,index赋值为1
@TestAnnotation(name = "array", index = 1)
public class Test {

    public static void main(String[] args) {
        // 声明一个class对象
        Class<?> cls;
        try {
            // 获取当前类的Class对象
            cls = Class.forName("Test");
            // 如果当前cls包含TestAnnotation注解将开始提取注解信息
            if(cls.isAnnotationPresent(TestAnnotation.class)) {
                // 获取当前cls上所有的注解信息
                Annotation[] anns = cls.getAnnotations();
                // 遍历所有获取到的注解信息
                for(Annotation ann : anns) {
                    // 如果有注解信息是TestAnnotation类型的将打印出TestAnnotation的name和index值
                    if(ann instanceof TestAnnotation) {
                        // 输出注解的信息
                        System.out.println("name = " + ((TestAnnotation)ann).name() + "---index = " + ((TestAnnotation)ann).index());
                    }
                }
            }
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
    }

}

// 运行时可以获取Annotation信息
@Retention(RetentionPolicy.RUNTIME)
// 可以修饰类、接口、枚举
@Target(ElementType.TYPE)
@interface TestAnnotation {
    // 字符串成员name,默认值为“Lyong”
    String name() default "Lyong";
    // int类型成员index,默认值为0
    int index() default 0;
}


例子有详细的注释信息,大家可以参考一下,联系一下其他类型的注解信息。

Java 8 新增的重复注解

在Java 8以前,同一个程序元素前最多只能使用一个相同类型的Annotation;如果需要在同一个元素前使用多个相同类型的Annotation,则必须使用Annotation“容器”。例如如下代码形式:

[code]@Results({@Result(name="failure",location="failed.jsp"),
@Result(name="success",location="succ.jsp")})
public Action FooAction{...}


上面代码中使用了两个@Result注解,但由于传统Java语法不允许多次使用@Result修饰同一个类,因此程序必须使用@Results注解作为两个@Result的容器——实质是,@Result注解只包含了一个名字为value、类型为Result[]的成员变量,程序指定的多个@Result作为@Results的value属性(数组类型)的数组元素。

从Java 8开始,上面语法可以得到简化:Java 8允许使用多个相同类型的Annotation修饰同一个类,因此上面代码可能(之所以说可能,是因为重复注解还需要对原来的注解进行改造)可简化为如下形式:

[code]@Result(name="failure",location="failed.jsp")
@Result(name="success",location="succ.jsp")
public Action FooAction{...}


我们来一步一步的演示一下重复注解的创建过程:

1.首先开发重复注解需要使用@Repeatable修饰

[code]@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Repeatable(Testables.class)
@interface Testable{
    String name() default "Lyong";
    int age() default 18;
}


上面我们创建了一个Testable Annotation,并声明为可在运行时获取信息,Testable可以修饰类、接口、枚举、并使用了@Repeatable来修饰此Annotation,表示可以使用多个Testable修饰同一个类。接下来我们看看“容器”的代码。

[code]@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@interface Testables{
    Testable[] value();
}


“容器”的不同只是成员变量是我们之前定义的Testable,也就是我们想在一个类上使用的多个Annotation,还有一点需要注意的是“容器”的@Retention的“声明周期”必须要比Testable的大。

接下来我们就可以使用重复注解了。

[code]@Testable(name="hello",age=20)
@Testable(name="world",age=21)
public class Test {

    public static void main(String[] args) {

    }
}


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

Java 8新增的Type Annotation

Java 8为ElementType枚举增加了TYPE_PARAMETER、TYPE_USE两个枚举值,这样就允许定义枚举时使用@Target(ElementType.TYPE_USE)修饰,这种注解被称为TypeAnnotation(类型注解),TypeAnnotation可用在任何用到类型的地方。

在Java 8以前,只能在定义各种程序元素(定义类、定义接口、定义方法、定义成员变量等)时使用注解。Java 8开始,TypeAnnotation可以在任何用到类型的地方使用。比如,允许在如下位置使用TypeAnnotation。

1.创建对象(用new关键字创建)。

2.类型转换。

3.使用implements实现接口。

4.使用throws声明抛出异常。

上面这些清醒都会用到类型,因此都可以使用类型注解修饰。

[code]// 定义类时使用Type Annotation
@NotNull
public class Test implements
        @NotNull /* implements时使用Type Annotation */Serializable {

    // 方法参数中使用Type Annotation
    public static void main(@NotNull String[] args)
            throws @NotNull FileNotFoundException
    // throws时使用Type Annotation
    {
        Object obj = "helloworld";
        // 强制类型转换时使用Type Annotation
        String str = (@NotNull String)obj;
        // 创建对象时使用Type Annotation
        Object win = new @NotNull JFrame("Title");
    }

    // 泛型中使用Type Annotation
    public void foo(List<@NotNull String> info){

    }
}

// 定义一个简单的Type Annotation,不带任何成员变量
@Target(ElementType.TYPE_USE)
@interface NotNull {
}


上面的代码都是可正常使用Type Annotation的例子,从这个示例可以看到,Java程序到处“写满”了Type Annotation,这种“无处不在”的Type Annotation可以让编译器执行更严格的代码检查,从而提高程序的健壮性。

需要指出的是,上面程序虽然大量使用了@NotNull注解,但这些注解暂时不会起任何作用——因为没有为这些注解提供处理工具。而且Java 8本身没有提供对Type Annotation执行检查的框架,因此如果需要让这些Type Annotation发挥作用,开发者需要自己实现Type Annotation检查框架。

幸运的是,Java 8提供了Type Annotation之后,第三方组织在发布他们的框架时,可能会随着框架一起发布Type Annotation检查工具,这样普通开发者即可直接使用第三方框架提供TYpe Annotation,从而让编译器执行更严格的检查,保证代码更加健壮。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: