Java注解篇


Java注解

Java5开始,Java增加对元数据的支持,也就是Annotation,不是一般的注释。这些标记在编译、类加载、运行时被读取,并执行相应处理。通过使用注解,开发人员在源文件中嵌入一些补充信息,进而代码分析和部署工具可以通过这些补充信息进行部署。某方面看,Annotation就像修饰符一样,可用于修饰包、方法和构造器、变量等,这些信息被储存在Annotation的”name=value”对中。

Annotation是一个接口,程序可以通反射来获取指定程序元素的Annotation对象,然后通过注解对象来取得里面的元数据。

基本注解

注解必须使用工具提取元数据,工具还会根据元数据增加额外的功能,这种处理访问注解的工具统称APT。5个注解都在Java.lang包下,5个基本注解如下:

  • @Override
  • @Deprecated
  • @SuppressWarnings
  • @SafeVarargs(Java7新增)
  • @FunctionalInterface(Java8新增)
限定重写父类:@Override

@Override就是用来指定方法覆盖的,它强制一个子类必须重写父类方法,告诉编译器检查这个方法,避免低级错误。注意的是:@Override只能修饰方法,不能修饰其他程序元素!

标识已过时:@Deprecated

该注解表示类、方法、接口已过时,当其他程序使用已过时的类、方法时,编译器将警告。

抑制编译器警告:@SuppressWarnings

顾名思义,注解让编译器不会发出警告,如果修饰在类上,则该类不会有任何警告出现,如果修饰方法,则该方法不会有任何警告出现。

@SuppressWarnings(value="unchecked")
public class SuppressTest{
    public static void main(String[] args){
        List<String> myList = new ArrayList();
    }//此处泛型语法警告将被抑制
}
Java7堆污染警告和@SafeVarargs

上次讲泛型的时候,把一个不带泛型的对象赋给一个带泛型的变量时,将有可能引起转换异常,这种错误的原因可以称为堆污染。有些时候,开发者不希望看到该警告,可以用以下三种方法抑制警告:

  • @SafeVarargs修饰该方法或构造器
  • @SuppressWarnings(“unchecked”)
  • 编译使用-Xlint:varargs
Java8的函数式接口与@FunctonalInterface

Java8规定:如果接口中只有一个抽象方法,该接口就是函数式接口。而该注解就是用来指定某个接口必须为函数式接口的,只能含有一个抽象方法。注意:该注解只能修饰接口!

元注解

JDK除了5个基本注解外,还提供了6个元注解,其中5个都用于修饰其他注解定义,其中的@Repeatable专门用于定义Java8新增的重复注解。

@Retention

只能用于修饰注解定义,用于指定注解可以保留多长时间。@Retention包含一个RetentionPolicy类型的Value成员变量,使用时指定值。value成员变量的值只能是如下三个:

  • RetentionPolicy.CLASS:编译器把注解记录在class文件中,当运行程序时,JVM不可获取注解信息。默认值!
  • RetentionPolicy.RUNTIME:编译器把注解记录在class文件中,当运行程序时,JVM可以获取注解信息,程序可以通过反射获取注解信息。
  • RetentionPolicy.SOURCE:注解只保留在源代码中,编译器之间丢弃。
@Retention(value=RetentionPolicy.RUNTIME)
public class Test{...}
//@Retention(RetentionPolicy.RUNTIME) 也可以,当成员变量为value时,可直接填入值
@Target

只能修饰一个注解定义,用于指定被修饰的注解能用于修饰哪些程序单元。。同样它也包含一个名为value的成员变量,其值有:

  • ElementType.ANNOTATION_TYPE:指定注解只能修饰注解
  • ElementType.CONSTRUCTOR:指定该注解只能修饰构造器
  • ElementType.FIELD:只能修饰成员变量
  • ElementType.LOCAL_VARIABLE:只能修饰局部变量
  • ElementType.METHOD:只能修饰方法
  • ElementType.PACKAGE:只能修饰包
  • ElementType.PARAMETER:修饰参数
  • ElementType.TYPE:可以修饰类、接口、注解或枚举定义

同样的操作,在括号内指定value值,可以省去name=value形式,直接填入值。

@Target(ElementType.FIELD)//只能修饰成员变量
@Documented

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

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
@Documented
public @interface Test{...}
//自定义一个注解,该注解信息将被提取进入API文档
@Inherited

指定被它修饰的Annotation具有继承性,即某个类使用了该Annotation,则其子类将自动被该Annotation修饰。

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Inherited
public @interface i{...}
//使用i注解的类,其子类自动含有该注解

自定义注解

上面已经提到了自定义注解,跟定义接口差不多,只不过在interface前添加@符号。通常自定义注解可以修饰任何程序元素,比如类、接口、方法等。然而我们可以根据Annotation是否包含成员变量,可以分为两类:

  • 标记注解:没有成员变量,仅提供标记信息。
  • 元数据注解:包含成员变量的Annotation,它们可以接收更多的元数据。

一旦在注解内定义成员变量后,使用时就应该指定成员变量的值,或者直接在注解内指定默认值,可使用default关键字。

public @interface MyTag{
    String name() default "James";
    int age() default 32;
    //int[] ages();
}
public class Test{
    @MyTag
    public void test{
        ...
    }
}

提取注解信息

Java使用Annotation接口来代表程序元素前的注解,该接口是所有注解的的父接口,同时在java.lang.reflect包下新增AnnotatedElement接口,该接口表示可以接收注解的程序元素。该接口的实现类主要有:

  • Class:类定义
  • Constructor:构造器定义
  • Field:类的成员变量
  • Method:类的方法
  • Package:类的包

包同时还包含一些反射功能的工具类,AnnotatedElement接口是所有程序元素的父接口,所以程序通过反射获取某个类的AnnotatedElement对象之后,就可以调用对象的几个方法来访问Anntation信息。

使用第一种的自定义注解,即没有成员变量的注解,它的作用是标记。如下:

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface Testable{}

上面的@Testable注解用于标记哪些方法是可以测试的,该注解可以作为JUnit测试的补充。

Java8新增注解

重复注解

在Java8以前,同一程序元素前最多只能使用一个类型的Annotation,如果需要使用多个相同类型的注解,则必须使用Annotation容器。Java8以前为如下形式:

@Results({@Result(name="James",location="james.jsp"),@Result(name="Kameron",location="kameron.jsp")})

@Results注解只包含一个名为value、类型为Result[]的成员变量,指定多个@Result作为value的属性的数组元素。改进后为:

@Result(name="James",location="james.jsp")
@Result(name="Kameron",location="kameron.jsp")

上述即为重复注解,可以被多次使用修饰某一程序元素。开发此类重复注解需要使用@Repeatable修饰。

新增TypeAnnotation注解

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

  • 创建对象时(new)
  • 类型转换时
  • 实现接口时
  • throws声明抛出异常时

以上情况都会用到类型,因此可以使用注解来修饰。

@Target(ElementType.TYPE_USE)
@NotNull
public class TypeTest{
    public static void main(@NotNull String[] args) throws @NotNull FileNotFoundException{
        Object a = new @NotNull String("菜鸡一号");
    }
    public void foo(List<@NotNull String) info){...}
}

要想让这些TypeAnnotation起作用,开发者需要自己实现Type Annotation检查框架或者使用第三方框架。

编译时处理注解

APT(Annotation Processing Tool)是一种注解处理工具,它对源代码进行检查,并找出源文件所包含的Annotation信息,做额外处理。使用APT的目的是简化开发者的工作量,因为APT可以在编译程序源代码的同时生成一些附属文件,比如源文件、类文件、XML文件等,内容与源代码文件相关。简而言之,APT可以替代对代码信息和附属文件的维护工作。

Java.exe工具有一个-processor选项,可指定一个Annotation处理器,如果指定了这个处理器,那么这个处理器会在编译时提取并处理源文件中的注解。每个注解处理器都需要实现javax.anntation.processing包下的Processor接口。不过实现该接口必须实现所有方法,因此会采用继承AbstractProcessor方式来实现注解处理器。一个处理器可以处理一种或多种Annotation类型!

javac -processor HibernateAnnotationProcessor Person.java
//路径下生成一个Person.hbm.xml文件

XML文件根据源代码文件中的Annotation生成,这是使用APT的结果!


文章作者: 流浪舟
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 流浪舟 !
评论
 上一篇
Java类加载篇 Java类加载篇
Java类加载这部分知识比较深入底层,将重点介绍类加载和反射,会提到JDK动态代理、AOP,反射等诸多知识点。当调用Java命令允许程序时,该命令会启动多个线程,它们都处于该Java虚拟机进程里。所有线程、变量处于同一个进程里,它们都使用J
2020-10-18
下一篇 
Java泛型篇 Java泛型篇
Java泛型上次讲了集合,就是放对象的容器,但是集合并不知道对象的具体数据类型,所以很容易发生异常。比如: List a = new ArrayList(); a.add("as"); a.add("end"); a.add(2); a.f
2020-10-13
  目录