进阶指南!深入理解Java注解

117 篇文章 5 订阅
13 篇文章 0 订阅

添加微信:code_7steps,备注“进群”,邀请你加入大牛云集的技术交流群!

你可以在Java开发过程中,遇到过需要将元数据(描述其他数据的数据)与类、方法或者其他元素相关联的情况。

例如,在开发过程中,需要识别大型应用程序中未完成的类。对于每个未完成的类,元数据可能包含未完成类的开发人员的项目、预计完成时间。

在Java 5之前,注释(Comments)是Java提供将元数据与应用程序元素相关联的唯一灵活机制。

但是,注释是一种非常非常不好的选择。

由于编译器会忽略它们,incident,注释在运行过程中是没有意义的。即使它们可用,也必须对文本进行解析,以获得关键的数据项。

但是,这对注释的规范性有严格的要求。

Java 5通过引入注解(annotations),将元数据与各种应用程序元素相关联起来,这种标准机制来改变了一切。

该机制包括四个部分:

  • 用于声明注解类型的@interface机制

  • 元注释类型,可用于标识注释类型适用的应用程序元素, 标识注解的生命周期(注释类型的实例)

  • 通过对Java Reflection API的扩展来支持注解处理

  • 标准注解类型

在本文中,我将向大家解释如何使用这些组件。

使用@interface声明注解类型

你可以通过@符号,加上interface这个保留字符来声明注解类型。

例如,下面示例,声明了一个简单的注解类型,你可以使用它用它来注解ThreadSafe代码:

// ThreadSafe.java
public@interface ThreadSafe
{
}

声明此注解类型之后,通过在此类型的实例前面加上@加上类型名称进行注解,在你认为线程安全的方法前面加上这种类型的实例。

下面示例中,其中main()方法带有@ThreadSafe注释:

// AnnDemo.java
publicclass AnnDemo
{
   @ThreadSafe
   public static void main(String[] args)
   {
   }
}

ThreadSafe实例除了注解类型名称外不提供其他元数据。

但是,你可以通过向此类型添加元素来提供元数据,其中元素是放置在注释类型的主体中的方法标题。

除了没有代码主体之外,元素还受到以下限制:

  • 方法标题不能声明参数

  • 方法标题不能提供throws子句

  • 方法标头的返回类型必须是原始类型(例如int),java.lang.String,java.lang.Class,枚举,注释类型或这些类型之一的数组。不能为返回类型指定其他类型。作为另一个示例,清单3给出了一个ToDo注释类型,其中包含三个元素,这些元素标识特定的编码作业,指定要完成该作业的日期,并命名负责完成该作业的编码器。

下面来看一下另外一个示例,给出了一个ToDo注释类型,其中包含三个元素,这些元素标识特定的编码任务,指定要完成该任务的日期,并命名负责完成该任务的人员。

// ToDo.java
public@interface ToDo
{
   int id();
   String finishDate();
   String coder() default "n/a";
}

请注意,每个元素都没有声明任何参数或throws子句,具有符合规范的返回类型(int或String),并以分号结尾。另外,最后一个元素显示可以指定默认返回值;如果注释未为元素分配值,则返回此值。

ToDo来注解类方法:

// AnnDemo.java
publicclass AnnDemo
{
   public static void main(String[] args)
   {
      String[] cities = { "New York", "Melbourne", "Beijing", "Moscow", 
                          "Paris", "London" };
      sort(cities);
   }
​
   @ToDo(id = 1000, finishDate = "10/10/2019", coder = "John Doe")
   static void sort(Object[] objects)
   {
   }
}

上述示例中,为每个参数分配了一个元数据项。例如,将1000分配给id。与coder不同,必须指定id和finishDate元素。否则,编译器将报告错误。未为编码器分配值时,它将采用默认值“ n / a”。

使用元注解(meta-annotation)

你可以注解多种类型,例如,类,方法,局部变量等。

但是,这种灵活性可能会带来问题。例如,可能只想将ToDo限制为方法,但是没有什么可以阻止ToDo用于注释其他应用类型元素的机制:

来看一个示例,

// AnnDemo.java
@ToDo("1000,10/10/2019,John Doe")
publicclass AnnDemo
{
   public static void main(String[] args)
   {
      @ToDo(value = "1000,10/10/2019,John Doe")
      String[] cities = { "New York", "Melbourne", "Beijing", "Moscow", 
                          "Paris", "London" };
      sort(cities);
   }
​
   @ToDo(value = "1000,10/10/2019,John Doe")
   static void sort(Object[] objects)
   {
   }
​
   @ToDo("1000,10/10/2019,John Doe")
   static boolean search(Object[] objects, Object key)
   {
      returnfalse;
   }
}

在清单7中,ToDo既用于注解AnnDemo类,又要勇于注解局部变量cities

这些错误的注解可能会使阅读你代码的人员感到困惑。

在需要缩小注释类型的灵活性的时代,Java在其java.lang.annotation包中提供了Target注释类型。

Target是元注解类型,它注释注释了注释类型,其注释那些注释了应用程序元素,例如类和方法。

它标识注释类型适用的应用程序元素的类型。

这些元素由TargetElementValue[], value[]元素标识。

java.lang.annotation.ElementType是一个枚举,其常量描述应用程序元素。

例如,CONSTRUCTOR适用于构造函数,而PARAMETER适用于参数。

下面实例中的ToDo注解中,将其限制为仅注解方法:

// ToDo.java
import java.lang.annotation.ElementType;
import java.lang.annotation.Target;
​
@Target({ElementType.METHOD})
public@interface ToDo
{
   String value();
}

其他元注解类型

Java 5引入了三种元注解类型,可以在java.lang.annotation包中找到它们:

  • Retention指示带有注释类型的注释要保留多长时间。

  • Documented表示由Javadoc和类似工具记录Documented注解的实例。

  • Inherited表示注解类型是自动继承的。

Java 8引入了java.lang.annotation.Repeatable元注解类型。Repeatable用于指示注解类型是可重复的。

换句话说,你可以将来自同一可重复注释类型的多个注释应用于一个application元素,如下所示:

@ToDo(value = "1000,10/10/2019,John Doe")
@ToDo(value = "1001,10/10/2019,Kate Doe")
static void sort(Object[] objects)
{
}

通过对Java Reflection API的扩展来支持注解处理

注解是要处理的,否则,它将没有任何意义。

Java 5扩展了Reflection API,以帮助你创建自己的注释处理工具。

例如,Class声明一个Annotation[] getAnnotations()方法,该方法返回一个java.lang.Annotation实例数组,该实例描述由Class对象描述的元素上存在的注解。

下面示例提供了一个简单的程序,该应用程序加载一个类文件,询问其ToDo注解的方法,并输出每个找到的注解的组件:

// AnnProcDemo.java
import java.lang.reflect.Method;
​
publicclass AnnProcDemo
{
   public static void main(String[] args) throws Exception
   {
      if (args.length != 1)
      {
         System.err.println("usage: java AnnProcDemo classfile");
         return;
      }
      Method[] methods = Class.forName(args[0]).getMethods();
      for (int i = 0; i < methods.length; i++)
      {
         if (methods[i].isAnnotationPresent(ToDo.class))
         {
            ToDo todo = methods[i].getAnnotation(ToDo.class);
            String[] components = todo.value().split(",");
            System.out.printf("ID = %s%n", components[0]);
            System.out.printf("Finish date = %s%n", components[1]);
            System.out.printf("Coder = %s%n%n", components[2]);
         }
      }
   }
}

验证仅指定了一个命令行参数(标识类文件)后,main()通过Class.forName()加载类文件,调用getMethods()返回java.lang.reflect.Method对象的数组 识别类文件中的所有公共方法,并处理这些方法。

编译此源代码(javac AnnProcDemo.java)。

在运行该应用程序之前,你需要一个合适的类文件,该文件的公共方法上带有@ToDo批注。

// ToDo.java
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
​
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public@interface ToDo
{
   String value();
}

编译修改后的AnnDemo

java AnnProcDemo AnnDemo

会得到如下输出:

ID = 1000
Finish date = 10/10/2019
Coder = John Doe
​
ID = 1000
Finish date = 10/10/2019
Coder = John Doe

标准注解类型

在Java 5中,与Target,Retention,Documented和Inherited一起引入了java.lang.Deprecated,java.lang.Override和java.lang.SuppressWarnings。

这三种注解类型被设计为仅在编译器上下文中使用,这就是为什么将其设置为SOURCE的原因。

Deprecated用于注解那些将要被弃用的元素,当程序不建议使用时,编译器会发出警告消息。

Override为重写其父类对应方法的子类方法添加注解。当子类方法未重新父类方法时,编译器将报告错误。

SuppressWarnings用于注解应用程序元素(以及这些应用程序元素中包含的所有元素),其中任何已命名的编译器警告(例如,unchecked)都应该被禁止。


干货推荐

为了方便大家,我花费了半个月的时间把这几年来收集的各种技术干货整理到一起,其中内容包括但不限于Python、机器学习、深度学习、计算机视觉、推荐系统、Linux、工程化、Java,内容多达5T+,我把各个资源下载链接整理到一个文档内,目录如下:

imgimgimgimgimgimgimgimgimgimgimgimgimgimgimgimg

所有干货送给大家,希望能够点赞支持一下!

https://pan.baidu.com/s/1eks7CUyjbWQ3A7O9cmYljA(提取码:0000)

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值