Java 注解(Java Annotation)

Java注解又称Java标注,JDK 5.0 引入的一种注释机制,是可加入源代码的特殊语法元数据(所谓元数据,就是描述数据的数据)。Java注解为在代码中添加信息提供了一种形式化的方法,使我们可以在编辑之后的某个时刻(如编译期、运行时等)非常方便的使用这些数据(目的是使用这些数据)。
Java语言中的类、方法、变量、参数和包等都可以被标注。在编译器生成类文件时,注解可以被嵌入到字节码中。和Javadoc不同,Java注解可以通过反射获取注解内容。Java虚拟机可以保留注解内容,在运行时可以获取到注解内容。当然也支持自定义注解。

注解的作用

注解是一个辅助类,它在 Junit、Struts、Spring 等工具框架中被广泛使用(注解在框架中广泛使用)。注解的主要作用有:
(1) 编译检查
注解具有"让编译器进行编译检查的作用"。如@SuppressWarnings, @Deprecated 和 @Override 都具有编译检查作用。以@Override为例,若某个方法被 @Override 的标注,则意味着该方法会覆盖父类中的同名方法。如果有方法被 @Override 标识,但父类中却没有"被 @Override 标注"的同名方法,则编译器会报错。
(2) 在反射中使用
在反射的 Class,Method,Field 等中,有许多于注解相关的接口。这也意味着,我们可以在反射中解析并使用注解。
(3) 根据注解生成帮助文档
通过给注解注解加上 @Documented 标签,能使该注解标签(可以将注解称为标签,常见术语如打标签就是指在某某某上使用xxx注解)出现在 javadoc 中。
(4) 功能扩展
注解支持通过自定义注解来实现一些其他功能。

内置注解

根据注解的创建主体,将Java中自带的注解称为内置注解,将除Java之外的注解称为自定义注解。
Java 定义了一套注解,共有 7 个。其中 3 个在 java.lang 中,另外 4 个在 java.lang.annotation 中。作用于代码的注解(声明在java.lang包)有:
(1) @Override。检查该方法是否是重写方法。如果发现其父类,或者是引用的接口中并没有该方法时,会报编译错误。
(2) @Deprecated。标记过时元素(如方法、字段、内部类等)。如果使用该注解标记的元素,会报编译警告。
(3) @SuppressWarnings。指示编译器去忽略注解中声明的警告。
作用在其他注解的注解(也称元注解,声明在java.lang.annotation中)有:
(1) @Retention。标记这个注解怎么保存,是只在代码中,还是编入class文件中,或者是在运行时可以通过反射访问。默认保存方式是编入class文件中
(2) @Documented。标记这个注解是否包含在用户文档中。
(3) @Target。标记这个注解作用的元素类型。如@Override注解作用于方法,那么对应的注解是@Target(ElementType.METHOD)。
(4) @Inherited。标记这个注解是继承于哪个注解类(默认情况下,注解不会没有继承于任何子类)。
注意,从 Java 7 开始,额外添加3个注解
(1) @SafeVarargs。作用于代码,Java 7 支持,用于忽略任何使用参数为泛型变量的方法或构造函数调用产生的警告。
(2) @FunctionalInterface。作用于代码,Java 8 支持,用于标识一个函数式接口
(3) @Repeatable。作用于注解,Java 8 支持,用于标识某个注解可以在同一个声明上使用多次

@Override

@Override作用于方法,用来标记该方法是用来重写(override)父类的方法的。一旦方法上使用该注解,则表示父类一定存在相同的方法(方法名、形参、返回值完全一致)或者是Object(所有类的基类)上的相同方法,否则编译器将报错。注意,子类重写父类的相同方法,不强制使用@Override注解,但是为了标明用途,建议重写父类相同方法时,统一使用@Override注解修饰。@Override定义如下:

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

}

可以看到,@Override注解作用于源代码的方法,在编译器编译阶段进行相关检查。更详细说明,可以参考源码注释。

@Deprecated

@Deprecated用来标记废弃元素。这个元素构造方法、字段、本地字段、方法、包、参数、类型(主要是内部类)。@Deprecated是一个编程元素。使用该注解标注的元素是开发该元素的人员(以下简称开发人员)不建议使用的元素,因为这个元素是个废弃的元素,表示开发人员不再维护该元素(即使使用中遇到问题)。通常使用这个元素会带来问题或存在更好的替代元素。在编程阶段,如果在使用了该元素或在non-deprecated代码中重写了该元素,编译器会警告不推荐使用该元素。@Deprecated定义如下:

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

阅读源码可知,Deprecated注解可以用在构造方法、字段、本地字段、方法、包、参数、类型(主要是内部类)中。

@SuppressWarnings

@SuppressWarnings作用于编辑阶段,用来告诉编译器忽略指定的警告。注意,当在超集中使用该注解后,其子元素也会生效。如在一个类上使用该注解忽略警告A,且在该类中定义的方法上使用该注解忽略警告B,那么在该方法上产生的警告A和警告B,均会被编译器忽略。所以,在使用该注解时,建议在最深层的嵌套元素里使用。如上述事例中,在方法中使用该注解,而不是在类上使用注解。除非,该类下的所有方法均需要忽略相同的告警。@SuppressWarnings定义如下:

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

阅读源码可知,SuppressWarnings注解可以用在类型、构造方法、字段、本地变量、方法、参数。
SuppressWarnings注解定义value()方法,该方法返回需要忽略的警告集合。注意,该集合支持重名,重复的警告将自动忽略。SuppressWarnings注解指定的无法识别的警告不会造成错误。对于无法识别的警告,编译器将忽略。不同编译器,其警告识别能力需单独明确。
@SuppressWarnings注解的常见使用方式如下:

(1) @SuppressWarnings("") 
如: @SuppressWarnings("unchecked"),@SuppressWarnings("unchecked","rawtypes")
(2) @SuppressWarnings({}) 
如: @SuppressWarnings({"unchecked","rawtypes"})
(3) @SuppressWarnings(value={}) 
如: @SuppressWarnings(value={"unchecked","rawtypes"})

习惯上,使用方式一。

常用的警告有:

@SuppressWarnings("unchecked")      // 抑制未检查的转化,例如集合没有指定类型的警告
@SuppressWarnings("unused")        // 抑制未使用的变量的警告
@SuppressWarnings("path")           // 抑制在类路径,原文件路径中有不存在的路径的警告
@SuppressWarnings("deprecation")    // 抑制使用了某些不赞成使用的类和方法的警告
@SuppressWarnings("fallthrough")    // 抑制switch语句执行到底没有break关键字的警告
@SuppressWarnings("serial")         // 抑制某类实现Serializable,但是没有定义serialVersionUID,这个需要但是不必须的字段的警告
@SuppressWarnings("all")           // 抑制全部类型的警告

@Retention

@Retention是一个元注解,用来表示标注的注解类保留多久,是只存在于代码中,还是编入class文件中,或者是在运行时可以通过反射访问。默认的保留策略是RetentionPolicy.CLASS。
注意,@Retention只有作用在注解类上才有效。Retention注解定义如下:

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

阅读源码可知,Retention注解定义value()方法,该方法返回特定保留策略(RetentionPolicy)。RetentionPolicy提供三种保留策略:

(1) SOURCE,代码级别。注解保留到编辑阶段。编译阶段,编译器会自动丢弃该注解。  
(2) CLASS,字节码级别。注解保留到编译阶段。编译器可以使用该注解。但是JVM不会将其加载到内存中。  
(3) RUNTIME,运行时级别。注解保留到运行时。可以在运行时通过反射访问该注解。

RetentionPolicy定义如下:

package java.lang.annotation;
public enum RetentionPolicy {
    SOURCE,
    CLASS,
    RUNTIME
}

@Documented

Documented是一个元注解,用于表示标注的注解类将包含在用户文档(如Javadoc等)中。这个注解主要用在会影响客户端的注解类上。如果注释类用Documented进行注释,则该注释类将成为使用该注释元素的公共API的一部分。Documented注解定义如下:

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

@Target

@Target是一个元注解,用来表示标注的注解类作用的元素类型。支持的元素类型可以参考Language Specification,或参考源码的ElementType枚举类。如果一个注解类没有显式使用Target注解,则该注解类可以作用于除类型之外的其他元素。Target注解定义如下:

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

注意,Target注解必须配合特定的ElementPolicy使用,不允许单独使用(会编译报错)。示例代码如下:

// 编译器提示:‘value' missing though required
// @Target()
public @interface CustomAnnotation {
    /**
     * default extension name
     */
    String value() default "";
}

Java支持的元素类型有:类型(Type,可以是Class、interface或枚举)、字段(FIELD,字段,也包括枚举字段),方法(METHOD)、参数(PARAMETER)、构造函数(CONSTRUCTOR)、局部变量(LOCAL_VARIABLE)、注解类型(ANNOTATION_TYPE)、包(PACKAGE)、类型参数(TYPE_PARAMETER)、类型使用(TYPE_USE)。其中, TYPE_PARAMETER 和 TYPE_USE 是Java 1.8新增的类型。

ElementType源码如下:

package java.lang.annotation;

public enum ElementType {
    TYPE,
    FIELD,
    METHOD,
    PARAMETER,
    CONSTRUCTOR,
    LOCAL_VARIABLE,
    ANNOTATION_TYPE,
    PACKAGE,
    TYPE_PARAMETER,
    TYPE_USE
}

ElementType用来指定注解类在哪些元素上是有效的。
注释可能出现的句法位置(Syntactic Location)可分为两类:声明上下文(Declaration Context,将注解应用于声明中)和类型上下文(Type Context,将注释应用于类型。该类型可用于声明或表达式中)
ElementType中适用于声明上下文的常量有:ANNOTATION_TYPE、CONSTRUCTOR、FIELD、LOCAL_VARIABLE、METHOD、PACKAGE、PARAMETER、TYPE、TYPE_PARAMETER。例如,使用 @Target(ElementType.FIELD)修饰的注释类只能用于字段声明(声明上下文)。

@Inherited

Inherited是一个元注解,用于标记注解类是可继承的。如果一个注解类使用Inherited声明,那么当用户查询指定注解声明时,如果当前类没有声明这个注解,则会自动查询这个类的父类。这一过程重复,直到到达Object为止。如果都没有该注释,则查询会返回没有这个注解的提示。
注意,Inherited标记的注解类只有作用在class上才有效(作用在interface或enum等将无效)。Inherited注解定义如下:

package java.lang.annotation;
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Inherited {
}

@SafeVarargs

@SafeVarargs在JDK 7中引入,主要目的是处理可变长参数中的泛型,此注解告诉编译器:在可变长参数中的泛型是类型安全的。可变长参数是使用数组存储的,而数组和泛型不能很好的混合使用。
SafeVarargs注解定义如下:

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.CONSTRUCTOR, ElementType.METHOD})
public @interface SafeVarargs {
}
public class AnnotationDemo {
    // @SafeVarargs
    public static <T> void showArgs(T... array) {
        for (T arg : array) {
            System.out.println(arg.getClass().getName() + ":" + arg);
        }
    }
}

不使用@SafeVarargs标记方法,且在编译源码时,添加**-Xlint:unchecked**参数,编译后结果如下:

xxx$ javac -Xlint:unchecked ./AnnotationDemo.java
./AnnotationDemo.java:31: 警告: [unchecked] 参数化 vararg 类型T的堆可能已受污染
    public static <T> void showArgs(T... array) {
                                         ^
  其中, T是类型变量:
    T扩展已在方法 <T>showArgs(T...)中声明的Object
1 个警告

Reifiable type和Non-reifiable type

Reifiable type (可具体化的类型) 是类型信息在运行时完全可用的类型。这包括基本类型,非泛型类型,原始类型和无界通配符的调用。
Non-reifiable type (不可具体化的类型) 是在编译时通过类型擦除移除信息的类型——未定义为无界通配符的泛型类型的调用。不可具体化的类型在运行时没有提供所有信息。不可具体化的类型的示例是 List 和 List,JVM 无法在运行时区分这些类型。

@FunctionalInterface

@FunctionalInterface 注解用来标识某个接口是函数式接口(所谓函数式接口就是指对于一个接口只能有一个抽象方法。这种类型的接口也称为SAM(Single Abstract Method)接口)。FunctionalInterface注解定义如下:

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface FunctionalInterface {

}

FunctionalInterface 注解只能作用于接口,该注解主要用于编译级错误检查。使用@FunctionalInterface注解修饰接口后,如果写的接口不符合函数式接口规范,则编译器会报错。
正确示例如下:

@FunctionalInterface
public interface CustomInferface {
    void test();
}

错误示例如下:

@FunctionalInterface
public interface CustomInferface {
    void test1();
    void test2();
}

上述示例会有如下编译异常提示:

Multiple non-overriding abstract methods found in interface xxxInferface

@Repeatable

Repeatable是 JDK 1.8新增的元注解。用于标识某个注解类可以在同一个声明上使用多次,也即可重复的注解类。

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Repeatable {
    Class<? extends Annotation> value();
}

自定义注解

除了Java语言内置注解,Java还支持创建自定义注解。Java自定义注解和创建一个接口相似,自定义注解的格式是以@interface为标志的。示例代码如下:

package io.github.courage007.annotation;

import java.lang.annotation.*;

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE})
public @interface SPI {
    /**
     * default extension name
     */
    String value() default "";
}

生成(javac命令)上述自定义注解的字节码后,反编译(javap命令)字节码文件,反编译代码如下:


public interface io.github.courage007.annotation.CustomAnnotation extends java.lang.annotation.Annotation {
    public abstract java.lang.String value();  
}

可以看到自定义注解SPI继承了Annotation接口。查看Annotation源码可知,Annotation接口是所有注解(内置注解和自定义注解)的基础接口。具体可参考Annotation源码和《Java Language Specification》9.6关于注解一节。

The direct superinterface of every annotation type is java.lang.annotation.Annotation.

Annotation 源码如下:

package java.lang.annotation;

public interface Annotation {

    boolean equals(Object obj);

    int hashCode();

    String toString();

    /**
     * Returns the annotation type of this annotation.
     * @return the annotation type of this annotation
     */
    Class<? extends Annotation> annotationType();
}

自定义注解依赖ElementTypeRetentionPolicy。也就是说,在定义注解时,要明确注解作用的元素类型以及该注解的保留策略。

在这里插入图片描述

参考

jdk 1.8.0源码
https://docs.oracle.com/javase/specs/index.html Java Language and Virtual Machine Specifications
https://www.cnblogs.com/jajian/p/9695055.html Java自定义注解
https://www.runoob.com/w3cnote/java-annotation.html Java 注解(Annotation)
https://blog.csdn.net/u012994320/article/details/83083392 Java中@SuppressWarnings(“unchecked”)的作用
https://cloud.tencent.com/developer/article/1353329 java注解用法详解——@SuppressWarnings
https://www.jianshu.com/p/c399490f24a2 @SuppressWarnings注解
https://www.cnblogs.com/peida/archive/2013/04/26/3038503.html 注解处理器
https://www.cnblogs.com/ludashi/p/6598806.html JavaEE开发之Spring中的条件注解、组合注解与元注解
https://zhuanlan.zhihu.com/p/95815525 在java中实现组合注解原理分析(注解继承)
https://www.cnblogs.com/uoar/p/8036642.html @Documented注释使用
https://cloud.tencent.com/developer/article/1579167 注解@Repeatable详解
https://pingfangx.github.io/java-tutorials/java/generics/nonReifiableVarargsType.html 不可具体化的类型
http://softlab.sdut.edu.cn/blog/subaochen/2017/04/safevarargs的用法/ SafeVarargs的用法
https://www.jianshu.com/p/6db80f2c2b6f Java @Repeatable
https://www.cnblogs.com/chenpi/p/5890144.html JAVA 8 函数式接口 - Functional Interface

原创不易,如果本文对您有帮助,欢迎关注我,谢谢 ~_~

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值