Java基础(15)——注解

常用注解

Java注解从Java1.5开始引入,注解就是代码中的特殊标记,告诉类要如何运作。注解的典型应用是:通过反射技术获得类中的注解,来决定如何运行类。

注解可以标记在类、属性、方法、变量等,并且一个地方可以同时标记多个注解。

先从一个简单的注解开始说起。

class SuperClass {
    public void test() {}
}

class SubClass extends SuperClass{
    @Override
    public void test() {}
}

@Override这个注解表示SubClass中的test()方法是覆写父类SuperClass中的test方法,阿里开发手册要求,凡是覆写的方法,强制加上@Override注解。可以防止书写错误,类似getObject()与 get0bject()的问题,一个是字母的 O,一个是数字的 0,加@Override 可以准确判断是否覆盖成功。以及接口或是父类的方法签名修改后,能够及时报错。

Java自带的注解中,常用的还有两个:@Deprecated、@SuppressWarnings。

注解作用
@Deprecated写在方法上,表示此方法是过时方法,不建议使用
@SuppressWarnings表示抑制指定警告

当一个方法被@Deprecated注解修饰,调用此方法时编译器会有相应提示。

class DeprecatedDemo {
    // 表示此方法为过时方法,不建议使用
    @Deprecated
    public void deprecate() {}
}

public class AnnotationDemo {
    public static void main(String[] args) {
        DeprecatedDemo deprecatedDemo = new DeprecatedDemo();
        // 此处提示deprecate()为过时方法,并显示删除线
        deprecatedDemo.deprecate();
    }
}

@SuppressWarnings会抑制编译器警告

public static void main(String[] args) {
    // 此处编译器警告:Variable 'str' is never used
    String str = "";
}
public static void main(String[] args) {
    // 加上@SuppressWarnings,警告消失
    @SuppressWarnings("unused")
    String str = "";
}

@SuppressWarnings可以同时抑制多类型警告,@SuppressWarnings({"unused", "unchecked"})

@SuppressWarnings也可以抑制所有类型的警告,@SuppressWarnings("all")

元注解

进入@Override注解源代码,可以看到如下代码:

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

有另外两个注解@Target与@Retention标记了@Override,它俩被称为元注解。Java中提供了四个对注解类型记性注解的注解类,它们被叫做元注解。

元注解说明
@Target描述被修饰的注解的使用范围
@Retention描述被修饰的注解的生存期
@Documented在使用javadoc工具为类生成帮助文档时,被修饰的注解会被保留
@Inherited被修饰的注解可以被子类获取

@Target

注解可以修饰包、类、方法、参数等,@Target限定了被修饰的注解的作用范围。@Target的取值范围为枚举值ElementType。

public enum ElementType {
    // 类,接口(包括注解),枚举
    TYPE,
    // 字段(成员变量),包括枚举实例
    FIELD,
    // 方法
    METHOD,
    // 方法参数
    PARAMETER,
    // 构造方法
    CONSTRUCTOR,
    // 局部变量
    LOCAL_VARIABLE,
    // 注解
    ANNOTATION_TYPE,
    // 包
    PACKAGE,
    // 类型参数,jdk8新增
    TYPE_PARAMETER,
    // 使用类型,jdk8新增
    TYPE_USE
}

例如,被@Target(ElementType.METHOD)修饰的注解只能用于方法上,用于其他地方会报错。

// @AnnotationMethod只能用于修饰方法
@Target(ElementType.METHOD)
@interface AnnotationMethod {}

// 报错,不可用于类型,'@AnnotationMethod' not applicable to type
@AnnotationMethod
public class AnnotationDemo {
    @AnnotationMethod
    public static void main(String[] args) {}
}

ElementType.TYPE_PARAMETER修饰的注解用于类型参数。

// 用于类型参数
@Target(ElementType.TYPE_PARAMETER)
@interface AnnotationTypePra {}

// 泛型类,声明泛型T,此处可以使用@AnnotationTypePra
class Gen<@AnnotationTypePra T> {
    // 此处使用报错,'@AnnotationTypePra' not applicable to field
    // 此时T已被视为一个普通的类型,而不是类型参数
    private @AnnotationTypePra T field;

    // 此处使用报错,'@AnnotationTypePra' not applicable to parameter
    // 此时T已被视为一个普通的类型,而不是类型参数
    public void set(@AnnotationTypePra T var){
        field = var;
    }

    // 泛型方法,<M>声明了泛型M,第一个@AnnotationTypePra可以使用
    // 第二个@AnnotationTypePra报错,'@AnnotationTypePra' not applicable to parameter
    // 此时M已被视为一个普通的类型,而不是类型参数
    public <@AnnotationTypePra M> void show(@AnnotationTypePra M var){
        System.out.println(var);
    }
}

ElementType.TYPE_PARAMETER修饰的注解可以使用的地方,ElementType.TYPE_USE修饰的注解都可以使用,并且可以用在任何使用类型的地方,void除外。

// 用于所有使用类型的地方
@Target(ElementType.TYPE_USE)
@interface AnnotationTypeUse {}

// 用在类上
@AnnotationTypeUse
public class AnnotationDemo {
    // 用于字段类型
    private @AnnotationTypeUse String str;
    
    // 第一个@AnnotationTypeUse报错,'void' type may not be annotated
    // 第二个@AnnotationTypeUse用于方法参数,可以使用
    public static @AnnotationTypeUse void main(@AnnotationTypeUse String[] args) {}
    
    // 用于返回值类型
    public @AnnotationTypeUse Integer getInt() {
        return 1;
    }
}

// 用于类型参数
class Gen<@AnnotationTypeUse T> {
    // 用于字段类型
    private @AnnotationTypeUse T field;
    
    // 用于方法参数
    public void set(@AnnotationTypeUse T var){
        field = var;
    }

    // 第一个@AnnotationTypeUse用于类型参数
    // 第二个@AnnotationTypeUse用于方法参数
    public <@AnnotationTypeUse M> void show(@AnnotationTypeUse M var){
        System.out.println(var);
    }
}
  • 若注解未被@Target修饰,那这个注解的作用范围是,除了类型参数(ElementType.TYPE_PARAMETER)以外的,其他所有ElementType枚举值的作用范围。
  • 若注解想要有多个作用范围,可以在@Target()中写多个值,用{}包裹并用逗号隔开
@Target({ElementType.FIELD, ElementType.CONSTRUCTOR, ElementType.METHOD})
  • 若注解被@Target({})修饰,那么该注解不可用于直接修饰任何元素,只能作为其他复杂注解的成员类型。
@Target({})
@interface AnnotationEmpty {}

@interface AnnotationTest {
    // 作为其他注解的成员类型
    AnnotationEmpty annotationEmpty();
}

@Retention

注解的生存期分为:只存在于源码中,保存到.class文件但是不被VM加载,运行期中可以获取。@Retention用来约束注解的生存期,取值范围为枚举值RetentionPolicy。

public enum RetentionPolicy {
    // 该类型注解只存于源码中,将被编译器丢弃
    SOURCE,
    // 该类型注解会被编译器保存到.class文件,但是不会被VM加载到内存中
    // 这是注解的默认生存期
    CLASS,
    // 该类型注解会被编译器保存到.class文件,并且会被VM加载到内存中,从而可以通过反射获取注解的相关信息
    RUNTIME
}

下面用代码演示@Retention,首先定义三个不同生存期的注解。

// 该类型注解只存在于源码中
@Retention(RetentionPolicy.SOURCE)
@interface SourceAnnotation {}

// 该类型注解会被保存到.class文件,但是不会被VM加载到内存
@Retention(RetentionPolicy.CLASS)
@interface ClassAnnotation {}

// 该类型注解会被VM加载到内存中,运行期可通过反射获取
@Retention(RetentionPolicy.RUNTIME)
@interface RuntimeAnnotation {}

写三个被不同生存期注解修饰的方法

package Annotation;

public class AnnotationDemo {

    @SourceAnnotation
    public void SourceMethod() {}

    @ClassAnnotation
    public void ClassMethod() {}

    @RuntimeAnnotation
    public void RuntimeMethod() {}
}

cmd中执行javac *.java,然后执行javap -verbose AnnotationDemo.class查看AnnotationDemo的字节码文件,其中部分字节码如下所示:

{
  public Annotation.AnnotationDemo();
    descriptor: ()V
    flags: ACC_PUBLIC
    Code:
      stack=1, locals=1, args_size=1
         0: aload_0
         1: invokespecial #1                  // Method java/lang/Object."<init>":()V
         4: return
      LineNumberTable:
        line 3: 0

  public void SourceMethod();
    descriptor: ()V
    flags: ACC_PUBLIC
    Code:
      stack=0, locals=1, args_size=1
         0: return
      LineNumberTable:
        line 8: 0

  public void ClassMethod();
    descriptor: ()V
    flags: ACC_PUBLIC
    Code:
      stack=0, locals=1, args_size=1
         0: return
      LineNumberTable:
        line 13: 0
    RuntimeInvisibleAnnotations:
      0: #11()

  public void RuntimeMethod();
    descriptor: ()V
    flags: ACC_PUBLIC
    Code:
      stack=0, locals=1, args_size=1
         0: return
      LineNumberTable:
        line 18: 0
    RuntimeVisibleAnnotations:
      0: #14()
}

通过上述字节码可以看到:

  • 文件中有四个方法,分别是默认无参构造方法+3个代码中定义的方法
  • .class文件并没有保存SourceMethod()方法的相关注解信息
  • ClassMethod()方法使用RuntimeInvisibleAnnotations描述注解信息,RuntimeMethod()方法使用RuntimeVisibleAnnotations描述注解信息。

在运行时查看注解信息

public class Test {
    public static void main(String[] args) throws ClassNotFoundException {
        // 获取AnnotationDemo的Class对象
        Class<?> clazz = Class.forName("Annotation.AnnotationDemo");
        // 通过Class对象获取所有public方法,包括继承的
        Method[] methods = clazz.getMethods();
        for (Method method : methods) {
            // 通过Method对象获取修饰此方法的注解
            Annotation[] annotations = method.getAnnotations();
            for (Annotation annotation : annotations) {
                System.out.println(method.getName() + "方法上的注解为:" + annotation);
            }
        }
    }
}

运行结果:

RuntimeMethod方法上的注解为:@Annotation.RuntimeAnnotation()

只有RuntimeMethod方法上的注解信息在运行期间被反射获取到。

@Documented

@Documented注解作用:在使用javadoc为类生成帮助文档时,是否保留此注解信息。

// 此注解未被@Documented修饰,不会在帮助文档中保留此注解信息
@interface WithoutDocAnnotation {}

// 此注解被@Documented修饰,会在帮助文档中保留此注解信息
@Documented
@interface DocumentedAnnotation {}

public class Test {
    @DocumentedAnnotation
    public void documentedTest() {

    }

    @WithoutDocAnnotation
    public void withoutDocumentedTest() {

    }
}

在cmd窗口执行javac Test.java,然后再执行javadoc -d doc Test.java,会在新建的doc文件夹中生成帮助文档,使用浏览器打开其中的index.html文件,可以看到以下内容。

被@Documented修饰的注解信息保留到了帮助文档中,未被@Documented修饰的注解信息则未保留下来。

@Inherited

被@Inherited修饰的注解会具有继承性,也就是在父类上使用的注解,可以被子类通过Class对象的getAnnotations()方法获取。

// @Inherited修饰注解,则此类型注解可以被子类获取
@Inherited
// 注意添加RetentionPolicy.RUNTIME,否则默认只存在于.class文件,运行期间无法通过反射获取
@Retention(RetentionPolicy.RUNTIME)
@interface InheritedAnnotation {}

@Retention(RetentionPolicy.RUNTIME)
@interface WithoutInheritedAnnotation {}

@InheritedAnnotation
class SuperClassA {}

class SubClassA extends SuperClassA{}

@WithoutInheritedAnnotation
class SuperClassB {}

class SubClassB extends SuperClassB{}

public class Test {
    public static void main(String[] args) throws ClassNotFoundException {
        // 获取SubClassA的Class对象
        Class<?> clazzA = Class.forName("Annotation.SubClassA");
        // 通过Class对象获取修饰类的所有注解
        Annotation[] annotationsA = clazzA.getAnnotations();
        for (Annotation annotation : annotationsA) {
            System.out.println(annotation);
        }

        Class<?> clazzB = Class.forName("Annotation.SubClassB");
        Annotation[] annotationsB = clazzB.getAnnotations();
        for (Annotation annotation : annotationsB) {
            System.out.println(annotation);
        }
    }
}

运行结果:

@Annotation.InheritedAnnotation()

自定义注解

上面在介绍元注解的时候,用到了自定义注解。

@Documented
@Inherited
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface AnnotationDemo {
    String name() default "default name";
}

自定义注解@AnnotationDemo,在帮助文档中会保留此注解信息,可以被子类通过反射获取(需要注解的生存期为Runtime),只能用来修饰类型(类,接口,包括注解,枚举)以及方法,生存期到运行期间。

在自定义注解@AnnotationDemo中,声明了一个String类型的name元素,默认值为"default name"。注意,注解中元素的声明需要使用类似于方法的方式,同时可选择使用default提供默认值。@AnnotationDemo的使用方式如下:

@AnnotationDemo(name = "annotation test")
class AnnotationTest {

}

在注解中可以使用的元素类型除了刚才的String,总共有下列几种:

  • 所有的基本类型(boolean, byte, char, short, int, long, float, double)
  • String
  • Class
  • enum
  • Annotation
  • 上述类型的数组

使用其他数据类型,会编译报错。声明注解元素时,可以使用基本类型,但是不能使用包装类型。注解可以作为注解元素的类型。

enum WeekEnum {
    WEEKDAY,
    WEEKEND
}

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface AnnotationDemo {
    String name() default "defaultName";
}

@interface AnnotationElementDemo {
    // 基本类型boolean作为注解内元素的类型
    boolean support() default false;
    // String作为注解内元素的类型
    String name() default "";
    // Class对象作为注解内元素的类型
    Class<?> clazz() default Void.class;
    // 枚举作为注解内元素的类型
    WeekEnum week() default WeekEnum.WEEKDAY;
    // 注解作为注解内元素的类型
    AnnotationDemo annotationDemo() default @AnnotationDemo(name = "defaultName01");
    // 数组作为注解内元素的类型
    int[] count();
}

自定义一个简单注解

public @interface AnnotationTest {
    String name() default "";
}

编译后反编译,获取到反编译的代码

public interface Annotation.AnnotationTest extends java.lang.annotation.Annotation {
  public abstract java.lang.String name();
}

可以看到,注解在编译后,会自动继承java.lang.annotation.Annotation接口,但我们无法通过在代码中直接继承此接口来实现注解功能。

赋值

使用注解时,对注解内元素进行赋值,方式是key=value。

public @interface AnnotationTest {
    String name() default "";
}

@AnnotationTest(name = "test01")
class TestClass {}

注解中有名为value的元素,当使用该注解时,若只需要给value进行赋值时,可以不使用key=value形式,只写value即可。当给value及其他元素赋值时,则必须需要key=value。

@Target(ElementType.METHOD)
public @interface AnnotationTest {
    int value() default 0;
    String name() default "";
}

class TestClass {
    // 只给value赋值,可以使用快捷方式
    @AnnotationTest(10)
    public void method01() {}

    // 给多个元素赋值,必须使用key=value
    @AnnotationTest(value = 20, name = "name01")
    public void method02() {}
}

注解与反射

为了能在运行时获取到注解相关的信息,Java在java.lang.reflect反射包下新增了AnnotatedElement接口,它主要用来表示目前在VM中运行的程序中已使用注解的元素,通过该接口提供的方法,可以利用反射技术读取注解的信息,反射中常用的Class类、Constructor类、Method类与Field类等都实现了这个接口,在运行期间都可以通过反射获取修饰他们的注解信息。

AnnotatedElement接口中常用的几个方法:

方法名称返回值说明
isAnnotationPresent(Class<? extends Annotation> annotationClass)boolean判断此元素是否被指定类型的注解修饰,是的话返回true,否则返回false
getAnnotation(Class<T> annotationClass)T(<T extends Annotation>)返回此元素的指定类型的注解(包括从父类继承的),若没有,则返回null
getAnnotations()Annotation[]返回修饰此元素的所有注解,包括从父类继承的
getDeclaredAnnotation(Class<T> annotationClass)T(<T extends Annotation>)返回此元素的指定类型的注解(不包括从父类继承的),若没有,则返回null
getDeclaredAnnotations()Annotation[]返回修饰此元素的所有注解,不包括从父类继承的

代码演示如下:

package Annotation;

import java.lang.annotation.*;
import java.util.Arrays;

@Inherited
@Retention(RetentionPolicy.RUNTIME)
@interface AnnotationA {}

@Retention(RetentionPolicy.RUNTIME)
@interface AnnotationB {}

@AnnotationA
class SuperDemo {}

@AnnotationB
class SubDemo extends SuperDemo{}

public class Test {
    public static void main(String[] args) {
        Class<?> clazz = SubDemo.class;
        // 是否被指定类型的注解修饰
        boolean annotationPresent = clazz.isAnnotationPresent(AnnotationB.class);
        System.out.println("SubDemo被AnnotationB类型注解修饰:" + annotationPresent);
        // 获取指定类型的注解,包括父类的
        AnnotationA annotation = clazz.getAnnotation(AnnotationA.class);
        System.out.println(annotation);
        // 获取全部注解,包括父类的
        Annotation[] annotations = clazz.getAnnotations();
        System.out.println(Arrays.toString(annotations));
        // 获取指定类型的注解,不包括父类的
        AnnotationA declaredAnnotation = clazz.getDeclaredAnnotation(AnnotationA.class);
        System.out.println(declaredAnnotation);
        // 获取全部注解,不包括父类的
        Annotation[] declaredAnnotations = clazz.getDeclaredAnnotations();
        System.out.println(Arrays.toString(declaredAnnotations));
    }
}

运行结果:

SubDemo被AnnotationB类型注解修饰:true
@Annotation.AnnotationA()
[@Annotation.AnnotationA(), @Annotation.AnnotationB()]
null
[@Annotation.AnnotationB()]

在运行期间,可以通过上述方法拿到相关的注解以及注解的相关值,从而对不同注解修饰的方法或类进行不同的处理。

@Retention(RetentionPolicy.RUNTIME)
@interface AnnotationValue {
    String name() default "name01";
    int count() default 0;
}

@AnnotationValue(name = "ClassTest01", count = 10)
class TestValue {
    @AnnotationValue(name = "MethodShow", count = 20)
    public void show() {}
}

public class Test {
    public static void main(String[] args) throws NoSuchMethodException {
        // 获取Class对象
        Class<TestValue> testValueClass = TestValue.class;
        // 判断是否被相关注解修饰
        boolean annotationPresent = testValueClass.isAnnotationPresent(AnnotationValue.class);
        if (annotationPresent) {
            // 获取相关注解
            AnnotationValue annotation = testValueClass.getAnnotation(AnnotationValue.class);
            // 输出相关注解的值
            System.out.println("name:" + annotation.name() + ". count:" + annotation.count());
        }
        // 获取Method对象
        Method show = testValueClass.getMethod("show");
        // 判断是否被相关注解修饰
        boolean annotationPresent1 = show.isAnnotationPresent(AnnotationValue.class);
        if (annotationPresent1) {
            // 获取相关注解
            AnnotationValue annotation = show.getAnnotation(AnnotationValue.class);
            // 输出相关注解的值
            System.out.println("name:" + annotation.name() + ". count:" + annotation.count());
        }
    }
}

运行结果:

name:ClassTest01. count:10
name:MethodShow. count:20
  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值