目录

Annotation(注解)

注解在 Java 1.5 添加,在学习面向对象时使用 IDEA 生成覆盖方法会在方法上一行添加 @Override,另一个地方就是在使用注释 /** @return **/ IDEA 也会生成对应 @ 开头注解。

注解是一种 tag,可以用在包、类、构造方法、方法、成员变量、形参、局部变量、注解上,这个 tag 里可传递 metadata。如果说注释是对代码做解释,那 metadata 就是对代码做补充,后续可以通过反射获取到 metadata 自己实现注解逻辑。比如后续学习 JavaWeb Spring 框架时,会发现大量使用注解做配置,以往配置需要写 XML,现在只用通过一行注解代替,大大提高效率。

JDK 常见内置注解

  • @Override,说明这个方法时重写的,如果没有重写编译会报错;
  • @Deprecated,废弃方法,在编译时会产生警告;
  • @SuppressWarnings,让编译器不产生警告,最好不要这样做。

@Override

创建 OverrideTest.java。

package learnAnnotation;

public class OverrideTest {
    @Override
    public String toString() {
        return super.toString();
    }
}

很明显用 @Override 说明 toString 方法是重写 Object 类 toString 方法,不加也是重写,那好处在哪?

此时添加个 returnStatus() 方法。

package learnAnnotation;

public class OverrideTest {
    @Override
    public String toString() {
        return super.toString();
    }

    @Override
    public boolean returnStatus() {
        return false;
    }
}

@Override 会报 Checked Exception,说 returnStatus() 方法没有重写或实现父类方法,这就很好防止防止自己写错方法名。

另一个好处,解决了父类更改导致的隐藏问题。如父类本身有 returnStatus 方法,某一天更改了参数或方法名而子类没跟进修改,这很有可能就导致编译器认为是子类自己新增的方法,而不是重写。

用了 @Override 就能强制检查当前方法是重写的,不是就抛异常,在以后重写都要加上让编译器兜底。

@Deprecated

使用 new Integer(""); IDEA 会出现在方法名上画上删除线,编译时也会提示使用过时 API,这说明此方法不推荐使用或者有更好的替代方案。

@Deprecated注解IDEA展示效果.png

实际对某个对象进行使用时,首先在注释里说明 @deprecated 为什么弃用,还传递了参数 since=9 指定在 Java 9 版本就不推荐使用。其实还有个参数 forRemoval 是指到了这个版本是否会删除,默认不填是 false。

/**
* ......
*
* @deprecated
* It is rarely appropriate to use this constructor.
* Use {@link #parseInt(String)} to convert a string to a
* {@code int} primitive, or use {@link #valueOf(String)}
* to convert a string to an {@code Integer} object.
*/
@Deprecated(since="9")
public Integer(String s) throws NumberFormatException {
    this.value = parseInt(s, 10);
}

用法也很简单,需要想让哪个内容废弃就再哪里添加即可。

package learnAnnotation;

@Deprecated
public class OverrideTest {
    @Deprecated
    public boolean tmpStat = true;

    @Deprecated(since = "11", forRemoval = true)
    public boolean returnStatus() {
        return false;
    }
}

class invokeTest {
    public static void main(String[] args) {
        System.out.println(new OverrideTest().tmpStat);
        System.out.println(new OverrideTest().returnStatus());
        System.out.println(new OverrideTest());
    }
}

@SuppressWarnings

几种警告。

  • unchecked,运行时异常
  • deprecation,废弃警告
  • removal,移除警告
  • all,所有警告

使用同样简单,只需要抑制一个警告时直接传递对应值即可。

@SuppressWarnings("unchecked")

抑制多个警告需要用花括号括起来。

@SuppressWarnings({"unchecked", "deprecation"})

自定义注解

先看 @Deprecated 源码怎么写来学习咋自定义注解。

package java.lang;

import java.lang.annotation.*;
import static java.lang.annotation.ElementType.*;

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(value={CONSTRUCTOR, FIELD, LOCAL_VARIABLE, METHOD, PACKAGE, MODULE, PARAMETER, TYPE})
public @interface Deprecated {
    /**
     * Returns the version in which the annotated element became deprecated.
     * The version string is in the same format and namespace as the value of
     * the {@code @since} javadoc tag. The default value is the empty
     * string.
     *
     * @return the version string
     * @since 9
     */
    String since() default "";

    /**
     * Indicates whether the annotated element is subject to removal in a
     * future version. The default value is {@code false}.
     *
     * @return whether the element is subject to removal
     * @since 9
     */
    boolean forRemoval() default false;
}

其中 @Deprecated 注解定义语法是。

public @interface Deprecated { ... }

头上其余以 @ 开头的也是注解,称作元注解。

@Documented
@Retention
@Target

Meta-Annotation(元注解)

给注解做注解称作元注解。

@Retention

@Retention() 定义注解保存状态。

值使用的是 java/lang/annotation/RetentionPolicy.java 定义注解。

  • SOURCE,只停留在源码中,一旦编译完就消失。
  • CLASS,不指定 @Retention() 修饰当前创建的注解,则采取 CLASS 作为默认值,编译完会写入 .class 字节码文件里,当字节码加载到内存中会被抛弃。
  • RUNTIME,也写入 .class 字节码文件,但注解会被加载进内存,能被反射机制读取。
@Target

@Target() 注解定义了当前注解可以在哪里使用。一旦用错地方会报错

值使用的是 java/lang/annotation/ElementType.java 枚举。

  • TYPE,指类、接口、枚举、注解。
  • FIELD,类里字段,通常是指成员变量
  • METHOD,方法
  • PARAMETER,方法形参和异常方法形参类型。
  • CONSTRUCTOR,构造方法
  • LOCAL_VARIABLE,本地局部变量
  • ANNOTATION_TYPE,注解
  • PACKAGE,包
  • MODULE,Java 9 支持模块。
  • TYPE_PARAMETER,Java 8 支持类型参数。
  • TYPE_USE,Java 8 支持类型。

注解声明(annotation type declaration)

注解定义语法由 InterfaceModifier(访问修饰符),@interface 关键字,注解名称(Identifier 标识符),注解体(AnnotationTypeBody)构成。

看起来和接口类似,只是 interface 前面加个 @ 做区分。

{InterfaceModifier} @interface TypeIdentifier AnnotationTypeBody

实际长成这样。

public @interface 注解名称 { ... }

仅仅定义了注解,如果不使用 @Target 元注解定义使用位置,则所有地方都能用。

元素声明

@Deprecated 在使用时你会发现可以传递参数,这和定义方法类似,在注解里不叫形参叫 Element。

AnnotationTypeBody 里声明 Element,有个括号看起来像定义方法。

{public} RetunType Identifier() [DefaultValue];

声明 Element 的返回类型只能使用以下类型。

  • primitive type
  • String
  • Class or an invocation of Class
  • enum type
  • annotation type
  • array type

创建注解 AnnotationTest.java

package learnAnnotation;

import java.lang.annotation.ElementType;
import java.lang.annotation.Target;

/**
 * 注解声明测试
 */
@Target(ElementType.CONSTRUCTOR)
@interface AnnotationTest {
    char character();

    int[] ids();

    String desc() default "";
}

创建了一个 ids 元素类型是数组,desc 元素类型是字符串,默认值是空字符串。@Target 指定只能在构造方法上用。

元素传递

创建注解示例程序 AnnotationInvoke.java。

package learnAnnotation;

public class AnnotationInvoke {
    public AnnotationInvoke(String tmpTest) {
        System.out.println(tmpTest);
    }

    public static void main(String[] args) {
        new AnnotationInvoke("1");
    }
}

下面元素传递都是放在构造方法上,不再说明。

多个元素传递

定义了几个 Element 调用注解时也要传几个 ElementValuePair。ElementValuePair 语法是 Identifier = ElementValue。有一种情况是例外,当属性指定了默认值就不用填。

@AnnotationTest(character = 'a', ids = 1, desc = "介绍")

由于 ids 是数组,只传递 1 个值可以直接用等号,多个值需要用花括号 {value1, value2, value3}

元素默认值传递

默认值可以省略不填就自动取定义的默认值,也可以填了覆盖。

// desc 取默认值
@AnnotationTest(char = 'a', ids = 1)

// desc 覆盖默认值
@AnnotationTest(char = 'a', ids = 1, desc = "覆盖默认值啦"})

这里 desc 就是空字符串,覆盖后为 "覆盖默认值啦"。

单个元素传递

当注解中只有一个属性并且名为 value。

@interface AnnotationTest { String value(); }

或者第一个属性为 value,后面是默认值的情况可以省略。

@interface AnnotationTest2{
    String value();

    int[] ids() default {};

    String desc() default "";
}

传参可以不指定元素名。

@AnnotationTest("String")
@AnnotationTest2("String")

也可以指定元素名。

@AnnotationTest(value = "String")
@AnnotationTest2(value = "String", ids = 1, desc = "介绍下")
数组元素传递

ids 是数组,向数组传递多个值,要用花括号包裹起来。

@AnnotationTest(char = 'a', ids = {1, 2}, desc = "介绍")

单个则不用。

@AnnotationTest(char = 'a', ids = 1, desc = "介绍")

反射获取注解

AnnotationTest.java 声明的注解。

package learnAnnotation;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * 注解声明测试
 */
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.CONSTRUCTOR)
@interface AnnotationTest {
    char character();

    int[] ids();

    String desc() default "";
}

AnnotationInvoke.java 使用注解的类。

package learnAnnotation;

public class AnnotationInvoke {
    @AnnotationTest(character = 'a', ids = 1, desc = "this is char desc")
    public AnnotationInvoke(String tmpTest) {
        System.out.println(tmpTest);
    }
}

获取注解步骤。

  1. 注解 @Retention 一定是 RUNTIME。
  2. 确定注解的位置,获取对象。如果在类上先获取类,方法就获取方法对象。
  3. 确认对象是否有注解。
  4. 获取注解对象。
  5. 通过注解对象,获取注解元素值。

实际获取获取注解 AnnotatedElement 接口定义了几个方法,Class,Constructor,Field, Method,Module,Package,Parameter 这几个反射类都有实现。

  • boolean isAnnotationPresent(Class<? extends Annotation> annotationClass),判断当前这个 annotationClass 注解是否存在,annotationClass 是注解 Class 对象。
  • <T extends Annotation> T getAnnotation(Class<T> annotationClass),传入注解 Class 对象获取注解对象。跟调方法类似 annotationObj.元素名(); 获取注解元素值。
  • <T extends Annotation> T getDeclaredAnnotation(Class<T> annotationClass),传入注解 Class 对象获取注解对象(忽略继承来的注解)。
  • Annotation[] getAnnotations(),获取所有注解。
  • Annotation[] getDeclaredAnnotations(),获取所有注解,忽略继承过来的注解(注解可以继承有疑问再查资料)。

reflectionAnnotation.java 反射获取注解的类。

package learnAnnotation;

import java.lang.annotation.Annotation;
import java.lang.reflect.Constructor;
import java.util.Arrays;

public class reflectionAnnotation {
    public static void main(String[] args) {
        // 1. 获取类
        Class<AnnotationInvoke> classObj = AnnotationInvoke.class;

        // 2. 获取构造方法
        try {
            Constructor<AnnotationInvoke> constructorObj = classObj.getDeclaredConstructor("tmpTest".getClass());

            // 3. 将注解 Class 对象传入 isAnnotationPresent(),确认构造方法对象上是否有注解。
            if (constructorObj.isAnnotationPresent(AnnotationTest.class)) {
                // 4. 将注解 Class 对象传入 getAnnotation(),获取单个注解。
                AnnotationTest annotationObj1 = constructorObj.getAnnotation(AnnotationTest.class);
                System.out.println("getAnnotation() 方法获取的注解:");
                System.out.println(annotationObj1.character());
                System.out.println(Arrays.toString(annotationObj1.ids()));
                System.out.println(annotationObj1.desc());
                System.out.println();

                // 4.1 将注解 Class 对象传入 getDeclaredAnnotation(),获取单个注解。
                AnnotationTest annotationObj2 = constructorObj.getDeclaredAnnotation(AnnotationTest.class);
                System.out.println("getDeclaredAnnotation() 方法获取的注解:");
                System.out.println(annotationObj2.character());
                System.out.println(Arrays.toString(annotationObj2.ids()));
                System.out.println(annotationObj2.desc());
                System.out.println();

                // 4.2 getDeclaredAnnotation() 获取所有注解
                Annotation[] allAnnotation = constructorObj.getAnnotations();
                System.out.println("getDeclaredAnnotation() 方法获取的注解:");
                System.out.println(Arrays.toString(allAnnotation));
                System.out.println();

                // 4.3 getDeclaredAnnotations() 获取所有注解
                Annotation[] annotationObjs = constructorObj.getDeclaredAnnotations();
                System.out.println("getDeclaredAnnotations() 方法获取的注解:");
                System.out.println(Arrays.toString(annotationObjs));
            }
        } catch (NoSuchMethodException e) {
            throw new RuntimeException(e);
        }
    }
}

成功获取到 AnnotationInvoke.java 定义的注解 @AnnotationTest(character = 'a', ids = 1, desc = "this is char desc")

getAnnotation() 方法获取的注解:
a
[1]
this is char desc

getDeclaredAnnotation() 方法获取的注解:
a
[1]
this is char desc

getDeclaredAnnotation() 方法获取的注解:
[@learnAnnotation.AnnotationTest(desc="this is char desc", character='a', ids={1})]

getDeclaredAnnotations() 方法获取的注解:
[@learnAnnotation.AnnotationTest(desc="this is char desc", character='a', ids={1})]

获取到注解就能根据是否存在注解或 ElementValue 处理应用。

问题

  1. @Override 注解作用范围是 @Retention(RetentionPolicy.SOURCE) 不能通过反射获取,怎么还能做检查呢?
  2. getDeclaredAnnotation()/getDeclaredAnnotations() 返回的 Annotation 对象,怎么获取注解元素值呢?API 里没有找到对应方法。

最近更新:

发布时间:

摆哈儿龙门阵