Java 核心技术点之注解

转自:http://www.open-open.com/lib/view/open1473649808122.html

什么是注解

我们都知道在Java代码中使用注释是为了向以后阅读这份代码的人解释说明一些事情,注解是注释的升级版,它可以向编译器、虚拟机等解释说明一 些事情。比如我们非常熟悉的@Override就是一种元注解,它的作用是告诉编译器它所注解的方法是重写父类的方法,这样编译器就会去检查父类是否存在 这个方法,以及这个方法的签名与父类是否相同。

也就是说,注解是用来描述Java代码的,它能够被编译器解析,注解处理工具在运行时也能够解析注解。我们在Java源文件中使用注释,是为了 以后我们或他人再来读这段代码时,能够更好地理解它。Javadoc工具可以解析我们在源代码中为类、方法、变量等添加的描述信息,并根据这些描述信息自 动生成一个HTML文档,只要我们为类、方法等添加的描述信息符合Javadoc所要求的语法,我们就能够使用Javadoc工具根据我们的描述信息自动 生成一个帮助文档。而注解比java注释和Javadoc要强大得多,它们三者之间的重大的区别在于,Java注释和Javadoc描述所发挥的作用仅仅 到编译时就止步了,而注解直到运行时都能够发挥作用。

我们知道,使用“transient”关键字可以告诉编译器这个域不可序列化。相比于用”transient“这样的关键字修饰一个属性,注解 为我们提供了为类/方法/属性/变量添加描述信息的更通用的方式,而这些描述信息对于开发者、自动化工具、Java编译器和Java运行时来说都是有意义 的,也就是说他们都能“读懂”注解信息。”transient“关键字是一个修饰符,而 注解也是一种修饰符 。除了传递信息,我们也可以使用注解生成代码。我们可以使用注解,然后让注解解析工具来解析它们,以此来生成一些”模板化“的代码。比如Hibernate、Spring、Axis这些框架大量使用了注解,来避免一些重复的工作。

元注解

元注解即用来描述注解的注解,比如以下代码中我们使用“@Target”元注解来说明MethodInfo这个注解只能应用于对方法进行注解:

@Target(ElementType.METHOD)public @interface MethodInfo {
  ...
}

下面我们来具体介绍一下几种元注解。

Documented

当一个注解类型被@Documented元注解所描述时,那么无论在哪里使用这个注解,都会被Javadoc工具文档化。我们来看一下它的定义:

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Documented {
}

我们从以上代码中可以看到, 定义注解使用@interface关键字, 这就好比我们定义类时使用class关键字,定义接口时使用interface关键字一样,注解也是一种类型。这个元注解被@Documented修饰, 表示它本身也会被文档化。@Retention元注解的值RetentionPolicy.RUNTIME表示@Documented这个注解能保留到运 行时;@Target元注解的值ElementType.ANNOTATION_TYPE表示@Documented这个注解只能够用来描述注解类型。

Inherited

表明被修饰的注解类型是自动继承的。具体解释如下:若一个注解类型被Inherited元注解所修饰,则当用户在一个类声明中查询该注解类型 时,若发现这个类声明中不包含这个注解类型,则会自动在这个类的父类中查询相应的注解类型,这个过程会被重复,直到该注解类型被找到或是查找完了 Object类还未找到。这个元注解的定义如下:

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Inherited {
}

我们可以看到这个元注解类型被@Documented所注解,能够保留到运行时,只能用来描述注解类型。

Retention

我们在上面已经见到个这个元注解,它表示一个注解类型会被保留到什么时候,比如以下代码表示Developer注解会被保留到运行时:

@Retention(RetentionPolicy.RUNTIME)
public @interface Developer {
  String value();
}

@Retention元注解的定义如下:

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Retention {
    RetentionPolicy value();
}

我们在使用@Retention时,后面括号里的内容即表示他的取值,从以上定义我们可以看到,取值的类型为RetentionPolicy,这是一个枚举类型,它可以取以下值:

  • SOURCE:表示在编译时这个注解会被移除,不会包含在编译后产生的class文件中;
  • CLASS:表示这个注解会被包含在class文件中,但在运行时会被移除;
  • RUNTIME:表示这个注解会被保留到运行时,在运行时可以JVM访问到,我们可以在运行时通过反射解析这个注解。

Target

这个元注解说明了被修饰的注解的应用范围,也就是被修饰的注解可以用来注解哪些程序元素,它的定义如下:

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Target {
    ElementType[] value();
}

从以上定义我们可以看到它也会保留到运行时,而且它的取值是为ElementType[]类型(一个数组,意思是可以指定多个值),ElementType是一个枚举类型,它可以取以下值:

  • TYPE:表示可以用来注解类、接口、注解类型或枚举类型;
  • PACKAGE:可以用来注解包;
  • PARAMETER:可以用来注解参数;
  • ANNOTATION_TYPE:可以用来注解 注解类型;
  • METHOD:可以用来注解方法;
  • FIELD:可以用来注解属性(包括枚举常量);
  • CONSTRUCTOR:可以用来注解构造器;
  • LOCAL_VARIABLE:可用来注解局部变量。

常见内建注解

Java本身内建了一些注解,下面我们来介绍一下我们在日常开发中比较常见的注解:@Override、@Deprecated、@SuppressWarnings。相信我们大家或多或少都使用过这三个注解,下面我们一起再重新认识一下它们。

@Override注解

我们先来看一下这个注解类型的定义:

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.SOURCE)
public @interface Override {
}

从它的定义我们可以看到,这个注解可以被用来修饰方法,并且它只在编译时有效,在编译后的class文件中便不再存在。这个注解的作用我们大家 都不陌生,那就是告诉编译器被修饰的方法是重写的父类的中的相同签名的方法,编译器会对此做出检查,若发现父类中不存在这个方法或是存在的方法签名不同, 则会报错。

@Deprecated

这个注解的定义如下:

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(value={CONSTRUCTOR, FIELD, LOCAL_VARIABLE, METHOD, PACKAGE, PARAMETER, TYPE})
public @interface Deprecated {
}

从它的定义我们可以知道,它会被文档化,能够保留到运行时,能够修饰构造方法、属性、局部变量、方法、包、参数、类型。这个注解的作用是告诉编译器被修饰的程序元素已被“废弃”,不再建议用户使用。

@SuppressWarnings

这个注解我们也比较常用到,先来看下它的定义:

@Target({TYPE, FIELD, METHOD, PARAMETER, CONSTRUCTOR, LOCAL_VARIABLE})
@Retention(RetentionPolicy.SOURCE)
public @interface SuppressWarnings {
    String[] value();
}

它能够修饰的程序元素包括类型、属性、方法、参数、构造器、局部变量,只能存活在源码时,取值为String[]。它的作用是告诉编译器忽略指定的警告信息,它可以取的值如下所示:

  • deprecation:忽略使用了废弃的类或方法时的警告;
  • unchecked:执行了未检查的转换;
  • fallthrough:swich语句块中case忘加break从而直接“落入”下一个case;
  • path:类路径或原文件路径等不存在;
  • serial:可序列化的类缺少serialVersionUID;
  • finally:存在不能正常执行的finally子句;
  • all:以上所有情况产生的警告均忽略。

这个注解的使用示例如下:

@SuppressWarning(value={"deprecation", "unchecked"})
public void myMethos() {
  ...
}

通过使用以上注解,我们告诉编译器忽略myMethod方法中由于使用了废弃的类或方法或是做了未检查的转换而产生的警告。

自定义注解

我们可以创建我们自己的注解类型并使用它。请看下面的示例:

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
@Inherited
public @interface MethodInfo {
    String author() default "absfree";
    String date();
    int version() default 1;
}

在自定义注解时,有以下几点需要我们了解:

  • 注解类型是通过”@interface“关键字定义的;
  • 在”注解体“中,所有的方法均没有方法体且只允许public和abstract这两种修饰符号(不加修饰符缺省为public),注解方法不允许有throws子句;
  • 注解方法的返回值只能为以下几种:原始数据类型), String, Class, 枚举类型, 注解和它们的一维数组,可以为方法指定默认返回值。

我们再把上面提到过的@SuppressWarnings这个注解类型的定义拿出来看一下,这个注解类型是系统为我们定义好的,它的定义如下:

@Target({TYPE, FIELD, METHOD, PARAMETER, CONSTRUCTOR, LOCAL_VARIABLE})
@Retention(RetentionPolicy.SOURCE)
public @interface SuppressWarnings {
    String[] value();
}

我们可以看到,它只定义了一个注解方法value(),它的返回值类型为String[],没有指定默认返回值。我们使用@SuppressWarnings这个注解所用的语法如下:

@SuppressWarnings(value={"value1", "value2", ...})

也就是在注解类型名称后的括号内为每个注解方法指定返回值就可以使用这个注解。下面我们来看看怎么使用我们自定义的注解类型@MethodInfo:

public class AnnotationTest {
    @MethodInfo(author="absfree", date="20160410")
    public static void main(String[] args) {
        System.out.println("Using custom annotation...");
    }
}

那么现在问题来了,我们使用的自定义注解对于编译器或是虚拟机来说是有意义的吗(编译器或是虚拟机能读懂吗)?显然我们什么都不做的话,编译器或者虚拟机是读不懂我们的自定义注解的。下面我们来介绍以下注解的解析,让编译器或虚拟机能够读懂我们的自定义注解。

注解的解析

编译时解析

编译时注解指的是@Retention的值为CLASS的注解,对于这类注解的解析,我们只需做以下两件事:

  • 自定义类继承 AbstractProcessor类;
  • 重写其中的 process 函数。

然后编译器在编译时会自动查找所有继承自 AbstractProcessor 的类,然后调用他们的 process 方法。因此我们只要做好上面两件事,编译器就会主动去解析我们的编译时注解。现在,我们把上面定义的MethodInfo的Retention改为 CLASS,我们就可以按照以下代码来解析它:

@SupportedAnnotationTypes({ "com.custom.customannotation.MethodInfo" })
public class MethodInfoProcessor extends AbstractProcessor {
  @Override
  public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment env) {
    HashMap<String, String> map = new HashMap<String, String>();
    for (TypeElement typeElement : annotations) {
      for (Element element : env.getElementsAnnotatedWith(typeElement)) {
        MethodInfo methodInfo = element.getAnnotation(MethodInfo.class);
        map.put(element.getEnclosingElement().toString(), methodInfo.author());
      }
    }
    return false;
  }
}

@SupportedAnnotationTypes注解描述了Processor要解析的注解的名字。process 函数的annotations参数表示 表示待处理的注解集,env表示当前或是之前的运行环境。process函数的返回值表示annotations中的注解是否被这个Processor接 受。

更加详细的介绍请参考 Android 利用 APT 技术在编译期生成代码

运行时注解解析

首先我们把MethodInfo注解类型中Retention的值改回原来的RUNTIME,接下来我们介绍如何通过反射机制在运行时解析我们的自定义注解类型。

java.lang.reflect包中有一个AnnotatedElement接口,这个接口定义了用于获取注解信息的几个方法:

//返回该程序元素的指定类型的注解,若不存在这个类型的注解则返回null
T getAnnotation(Class annotationClass) 

//返回修饰该程序元素的所有注解
Annotation[] getAnnotations() 

//返回直接修饰该元素的所有注解
Annotation[] getDeclaredAnnotations() 

//当该程序元素被指定类型注解修饰时,返回true,否则返回false
boolean isAnnotationPresent(Class<? extends Annotation> annotationClass)

解析我们上面的自定义注解MethodInfo的相关示例代码如下(AnnotationParser.java):

public static void main(String[] args) {
    try {
        Class cls = AnnotationTest.class;
        for (Method method : cls.getMethods()) {
        	MethodInfo methodInfo = method.getAnnotation(MethodInfo.class);
        	if (methodInfo != null) {
        		System.out.println("method name:" + method.getName());
        		System.out.println("method author:" + methodInfo.author());
        		System.out.println("method date:" + methodInfo.date());
        		System.out.println("method version:" + methodInfo.version());
        	}
        }
    } catch (Exception e) {
    	e.printStackTrace();
    }
}

运行以上代码我们可以得到以下输出:

这说明我们已经成功解析了自定义注解。关于注解有点我们需要明确的是,作为描述代码本身的一种元数据, 注解是一种”被动“的信息。也就是说,必须由编译器或虚拟机来“主动”解析它,它才能发挥自己的作用。

参考资料

  1. Java Documention
  2. 公共技术点之Java注解
  3. Java 注解

来自:http://www.jianshu.com/p/8673bc2d5dec

时间: 2024-10-05 15:47:30

Java 核心技术点之注解的相关文章

Java核心技术点之注解

本博文是对Java中注解相关知识点的简单总结,若有叙述不清晰或是不准确的地方,希望大家可以指正,谢谢大家:) 一.什么是注解 我们大家都知道Java代码中使用注释是为了向以后阅读这份代码的人解释说明一些事情,注解是注释的升级版,它可以向编译器.虚拟机等解释说明一些事情.比如我们非常熟悉的@Override就是一种元注解,它的作用是告诉编译器它所注解的方法是重写父类的方法,这样编译器就会去检查父类是否存在这个方法,以及这个方法的签名与父类是否相同. 也就是说,注解是描述Java代码的代码,它能够被

《Java核心技术 卷II 高级特性 原书第10版》高清pdf

<Java核心技术 卷II 高级特性 原书第10版> 本书是Java领域有影响力和价值的著作之一,由拥有20多年教学与研究经验的Java技术专家撰写(获Jolt大奖),与<Java编程思想>齐名,10余年全球畅销不衰,广受好评.第10版根据JavaSE8全面更新,同时修正了第9版中的不足,系统全面讲解了Java语言的核心概念.语法.重要特性和开发方法,包含大量案例,实践性强. ? 百度网盘链接: https://pan.baidu.com/s/1aZ9KUgxwHNQJMH6X7h

Java核心技术之Java概述与开发环境搭建

Java核心技术之Java概述与开发环境搭建 Java核心技术 1.1 浅谈计算机语言 1.1 计算机语言发展史 1.1.2 编程语言应用场景 1.2 Java概述 1.2.1 Java发展历史 1.2.2 Java的特性 1.2.3 Java技术体系平台 1.3 Java程序员的必备环境 1.4 理解JDK,JRE和JVM三者之间的关系 1.5 macOS配置Java环境 1.5.1 macOS下载JDK 1.5.2 macOS下JDK安装 1.5.3 macOS下JDK配置 1.6 Visu

Java复习——枚举与注解

枚举 枚举就是让某些变量的取值只能是若干固定值中的一个,否则编译器就会报错,枚举可以让编译器在编译阶段就控制程序的值,这一点是普通变量无法实现的.枚举是作为一种特殊的类存在的,使用的是enum关键字修饰 枚举常量 枚举常量默认都是使用static final修饰的,所以语法建议使用大写,一个枚举类在第一次被实例化的时候,这些常量就会被创建,这些常量都是枚举类子类的对象 public enum WeekDay{ //每一个枚举的元素(枚举常量)就是一个枚举类子类的对象,是使用static fina

《Java核心技术 卷1 基础知识 原书第9版》pdf

下载地址:网盘下载 内容简介 编辑 CayS.Horstmann等编著,公飞编译的<Java核心技术>(CoreJava)自第1版出版以来,一直备受广大Java程序设计人员的青睐,畅销不衰,是Java经典书籍.第8版针对JavaSE6平台进行了全面更新,囊括了Java平台标准版(JavaSE/J2SE)的全部基础知识,提供了大量完整且具有实际意义的应用实例,详细介绍了Java语言基础知识.面向对象程序设计.接口与内部类.事件监听器模型.swing图形用户界面程序设计.打包应用程序.异常处理.登

java核心技术学习笔记之一程序设计概述

Java 核心技术之一程序设计概述 一.   Java语言的特点 简单行 :取经于C++,排除了C++不常用的指针.结构等,增加垃圾回收. 面向对象:与C++不同是单继承,但是可以继承多接口.完全面向对象的语言: 网络技能:Socket,FTP,Http,URL编程简单: 健壮性:避免指针错误使用: 安全性:构建防病毒防篡改系统: 体系结构中立:字节码: 可移植性: 解释性:可以解释任何移植了的字节码: 高性能: 多线程 动态性: 二.   Internet 和 Java Applet 在网页中

Java核心技术-4-对象与类

4 对象与类 4.1 面向对象程序设计概述 1 类 封装(数据隐藏):将数据和行为组合在一个包中,并对对象使用者隐藏数据的实现方式. 对象中的数据成为实例域,操纵数据的过程称为方法. 2 对象 对象的三个主要特性:对象的行为behavior,对象的状态state,对象标识identity.3 识别类 4 类之间的关系 依赖uses-a,一个类的方法操纵另一个类的对象 聚合has-a,一个类的对象(其数据域)包含另一个类的对象 继承is-a,类A扩展类B. 4.2 使用预定义类 1 对象与对象变量

java学习之路之java核心技术 1.1

第一本学习的书就选它了,java核心技术(第9卷)卷1,先要打牢基础,文笔不怎么好,就写写读书心得什么的,为了能及时写出想法,打算读一节写一节,怕忘了.下面开始. 这章大概介绍了下java,摘一句,常常希望有一种语言,它具有令人赏心悦目的语法和易于理解的语义,java恰恰满足了这些要求.

[基础] Java目录(摘自Java核心技术·卷1 基础知识)

Java核心技术·卷1 基础知识(原书第9版) 第1章 Java程序设计概述 1.1 Java程序设计平台 1.2 Java"白皮书"的关键术语 1.2.1 简单性 1.2.2 面向对象 1.2.3 网络技能 1.2.4 健壮性 1.2.5 安全性 1.2.6 体系结构中立 1.2.7 可移植性 1.2.8 解释型 1.2.9 高性能 1.2.10 多线程 1.2.11 动态性 1.3 Java applet与Internet 1.4 Java发展简史 1.5 关于Java的常见误解