浅析 Java 注解(Annotation)

ava 5之后可以在源代码中嵌入一些补充信息,这种补充信息称为注解(Annotation),例如在方法覆盖中使用过的@Override注解,注解都是@符号开头的。

注解并不能改变程序运行的结果,不会影响程序运行的性能。有些注解可以在编译时给用户提示或警告,有的注解可以在运行时读写字节码文件信息。

五个基本注解

无论是哪一种注解,本质上都一种数据类型,是一种接口类型。到Java 8为止Java SE提供11种内置注解。其中有 5 种是基本注解,它们来自于java.lang包。有6个是元注解(Meta Annotation),它们来自于java.lang.annotation包,自定义注解会用到元注解。

元注解就是负责注解其他的注解。

基本注解包括:@Override、@Deprecated、@SuppressWarnings、@SafeVarargs和@FunctionalInterface。下面逐一介绍一下。

@Override只能用于方法,子类覆盖父类方法(或者实现接口的方法)时可以@Override注解。编译器会检查被@Override注解的方法,确保该方法父类中存在的方法,否则会有编译错误。

package java.lang;

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

@Deprecated用来指示API已经过时了

package java.lang;

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

@SuppressWarnings 用来抑制编译器警告,如果你确认程序中的警告没有问题,可以不用理会。但是就是不想看到这些警告,可以使用@SuppressWarnings注解消除这些警告。

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

@SafeVarargs 例如在传递可变参数,参数是非泛型集合

     public class HelloWorld {

         public static void main(String[] args) {
             // 传递可变参数,参数是泛型集合
             display(10, 20, 30);                            
             // 传递可变参数,参数是非泛型集合
             display("10", 20, 30);// 会有编译警告           ①

         }

         public static <T> void display(T... array) {        ③
             for (T arg : array) {
                 System.out.println(arg.getClass().getName() + ":" + arg);
             }
         }
     }

在可变参数display前添加@SafeVarargs注解。当然可用也可以使用@SuppressWarnings("unchecked")注解,但@SafeVarargs注解更适合。

         @SafeVarargs
         public static <T> void display(T... array) {
             for (T arg : array) {
                 System.out.println(arg.getClass().getName() + ":" + arg);
             }
         }
package java.lang;

import java.lang.annotation.*;

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.CONSTRUCTOR, ElementType.METHOD})
public @interface SafeVarargs {}

@FunctionalInterface 在Java 8增加的,用于接口的注解,声明接口是函数式接口,在前面讲解Lambda表达式时介绍过函数式接口,有关@FunctionalInterface注解不再赘述。

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

六个元注解

元注解包括:@Documented、@Target、@Retention、@Inherited、@Repeatable和@Native。元注解是为其他注解进行说明的注解,当自定义一个新的注解类型时,其中可以使用元注解。这一节先介绍一下这几种元注解含义,下一节在自定义注解中详细介绍它们的使用的。

  1. @Documented

如果在一个自定义注解中引用@Documented注解,那么该注解可以修饰代码元素(类、接口、成员变量和成员方法等),javadoc等工具可以提取这些注解信息。

  1. @Target

@Target注解用来指定一个新注解的适用目标。@Target注解有一个成员(value)用来设置适用目标,value是java.lang.annotation.ElementType枚举类型的数组,ElementType描述Java程序元素类型,它有10个枚举常量,如表所示。

image
  1. @Retention

@Retention注解用来指定一个新注解的有效范围,@Retention注解有一个成员(value)用来设置保留策略,value是java.lang.annotation.RetentionPolicy枚举类型,RetentionPolicy描述注解保留策略,它有3个枚举常量,如表所示。

image
  1. @Inherited

@Inherited注解用来指定一个新注解可以被继承。假定一个类A被该新注解修饰,那么这个A类的子类会继承该新注解。

  1. @Repeatable

@Repeatable注解是Java 8新增加的,它允许在相同的程序元素中重复注释,可重复的注释必须使用@Repeatable进行注释。

  1. @Native

@Native注解一个成员变量,指示这个变量可以被本地代码引用。常常被代码生成工具使用。

自定义注解

如果前面的Java SE提供的11个内置注解不能满足你的需求,可以自定义注解,注解本质是一种接口,它是java.lang.annotation.Annotation接口的子接口,是引用数据类型。

新建注解类

声明自定义注解可以使用@interface关键字实现,最简单形式的注解示例代码如下:

package qy.likai.java;

public @interface MyAnnotation {
}

注意 关于注解源程序文件与类一样,一个源程序文件中可以声明多个注解,但只能有一个是公有访问权限的,源程序文件命名与公有访问权限的注解名一致。

Marker注解中不包含任何的成员,这种注解称为标记注解(Marked Annotation),基本注解中的@Override就属于标记注解。根据需要注解中可以包含一些成员,示例代码如下:

package qy.likai.java;

public @interface MyAnnotation {

    String value();

// 注解中的成员也可以有默认值,示例代码如下:
//         int count() default 0;
}

读取运行时注解信息

注解是为工具读取信息而准备的。有些工具可以读取源代码文件中的注解信息;有的可以读取字节码文件中的注解信息;有的可以在运行时读取注解信息。但是读取这些注解信息的代码都是一样的,区别只在于自定义注解中@Retention的保留策略不同

读取注解信息需要反射相关API,Class类如下方法:

  • A getAnnotation(Class annotationClass):如果此元素存在annotationClass类型的注解,则返回注解,否则返回null。
  • Annotation[] getAnnotations():返回此元素上存在的所有注解
  • Annotation[] getDeclaredAnnotations():返回直接存在于此元素上的所有注解。与getAnnotations()区别在于该方法将不返回继承的注释
  • boolean isAnnotationPresent(Class<? extends Annotation> annotationClass):如果此元素上存在annotationClass类型的注解,则返回true,否则返回false。
  • boolean isAnnotation():如果此Class对象表示一个注解类型则返回true。
package qy.likai.java;

import java.lang.annotation.Annotation;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.reflect.Field;
import java.lang.reflect.Method;

@MyAnnotation("type")
public class AnnotationTest {
    @MyAnnotation("field")
    private String title;

    private String subtitle;

    @MyAnnotation("constructor")
    private AnnotationTest(){}

    public AnnotationTest(String title) {
        this.title = title;
    }

    public static void main(@MyAnnotation("parameter") String[] args) {
        testAnnotation();
    }

    private static void testAnnotation() {
        @MyAnnotation("localVariable")
        String str = "hello MyAnnotation";

        // 读取类注解
        final Annotation[] annotations = AnnotationTest.class.getAnnotations();
        if (AnnotationTest.class.isAnnotationPresent(MyAnnotation.class)) {
            MyAnnotation ann = AnnotationTest.class.getAnnotation(MyAnnotation.class);
            System.out.printf("类%s,读取注解描述: %s \n",
                    AnnotationTest.class.getName(), ann.value());
        }

        // 读取成员方法的注解信息
        Method[] methods = AnnotationTest.class.getDeclaredMethods();
        for (Method method : methods) {
            if (method.isAnnotationPresent(MyAnnotation.class)) {
                MyAnnotation ann = method.getAnnotation(MyAnnotation.class);
                System.out.printf("方法%s,读取注解描述: %s \n",
                        method.getName(), ann.value());
            }
        }

        // 读取成员变量的注解信息
        Field[] fields = AnnotationTest.class.getDeclaredFields();
        for (Field field : fields) {
            if (field.isAnnotationPresent(MyAnnotation.class)) {
                MyAnnotation ann = field.getAnnotation(MyAnnotation.class);
                System.out.printf("成员变量%s,读取注解描述: %s \n",
                        field.getName(), ann.value());
            }
        }

    }

    @MyAnnotation(value = "method")
    @Override
    public String toString() {
        return super.toString();
    }
}

// 只有为 RUNTIME,才能动态读取信息。 默认级别为 CLASS
@Retention(RetentionPolicy.RUNTIME)
@interface MyAnnotation {
    String value();
}

元注解中RetentionPolicy的三个级别

1、RetentionPolicy.SOURCE:注解只保留在源文件,当Java文件编译成class文件的时候,注解被遗弃; 2、RetentionPolicy.CLASS:注解被保留到class文件,但jvm加载class文件时候被遗弃,这是默认的生命周期; 3、RetentionPolicy.RUNTIME:注解不仅被保存到class文件中,jvm加载class文件之后,仍然存在;

这3个生命周期分别对应于:Java源文件(.java文件) ---> .class文件 ---> 内存中的字节码。

那怎么来选择合适的注解生命周期呢?

首先要明确生命周期长度 SOURCE < CLASS < RUNTIME ,所以前者能作用的地方后者一定也能作用。一般如果需要在运行时去动态获取注解信息,那只能用 RUNTIME 注解;如果要在编译时进行一些预处理操作,比如生成一些辅助代码(如 ButterKnife),就用 CLASS注解;如果只是做一些检查性的操作,比如 @Override 和 @SuppressWarnings,则可选用 SOURCE 注解。

源码下载

gitee.com/kaiLee/java…

参考

  • Java从小白到大牛-关东升 (作者)-第 27 章 注解(Annotation)-图灵社区

www.ituring.com.cn/book/tupuba

  • 自定义注解之运行时注解(RetentionPolicy.RUNTIME)Rukey7的博客-CSDN博客@retention(retentionpolicy.runtime)
全部评论

相关推荐

头像
10-13 18:10
已编辑
东南大学 C++
。收拾收拾心情下一家吧————————————————10.12更新上面不知道怎么的,每次在手机上编辑都会只有最后一行才会显示。原本不想写凉经的,太伤感情了,但过了一天想了想,凉经的拿起来好好整理,就像象棋一样,你进步最快的时候不是你赢棋的时候,而是在输棋的时候。那废话不多说,就做个复盘吧。一面:1,经典自我介绍2,项目盘问,没啥好说的,感觉问的不是很多3,八股问的比较奇怪,他会深挖性地问一些,比如,我知道MMU,那你知不知道QMMU(记得是这个,总之就是MMU前面加一个字母)4,知不知道slab内存分配器-&gt;这个我清楚5,知不知道排序算法,排序算法一般怎么用6,写一道力扣的,最长回文子串反问:1,工作内容2,工作强度3,关于友商的问题-&gt;后面这个问题问HR去了,和中兴有关,数通这个行业和友商相关的不要提,这个行业和别的行业不同,别的行业干同一行的都是竞争关系,数通这个行业的不同企业的关系比较微妙。特别细节的问题我确实不知道,但一面没挂我。接下来是我被挂的二面,先说说我挂在哪里,技术性问题我应该没啥问题,主要是一些解决问题思路上的回答,一方面是这方面我准备的不多,另一方面是这个面试写的是“专业面试二面”,但是感觉问的问题都是一些主管面/综合面才会问的问题,就是不问技术问方法论。我以前形成的思维定式就是专业面会就是会,不会就直说不会,但事实上如果问到方法论性质的问题的话得扯一下皮,不能按照上面这个模式。刚到位置上就看到面试官叹了一口气,有一些不详的预感。我是下午1点45左右面的。1,经典自我介绍2,你是怎么完成这个项目的,分成几个步骤。我大致说了一下。你有没有觉得你的步骤里面缺了一些什么,(这里已经在引导我往他想的那个方向走了),比如你一个人的能力永远是不够的,,,我们平时会有一些组内的会议来沟通我们的所思所想。。。。3,你在项目中遇到的最困难的地方在什么方面4,说一下你知道的TCP/IP协议网络模型中的网络层有关的协议......5,接着4问,你觉得现在的socket有什么样的缺点,有什么样的优化方向?6,中间手撕了一道很简单的快慢指针的问题。大概是在链表的倒数第N个位置插入一个节点。————————————————————————————————————10.13晚更新补充一下一面说的一些奇怪的概念:1,提到了RPC2,提到了fu(第四声)拷贝,我当时说我只知道零拷贝,知道mmap,然后他说mmap是其中的一种方式,然后他问我知不知道DPDK,我说不知道,他说这个是一个高性能的拷贝方式3,MMU这个前面加了一个什么字母我这里没记,别问我了4,后面还提到了LTU,VFIO,孩子真的不会。
走呀走:华子二面可能会有场景题的,是有些开放性的问题了
点赞 评论 收藏
分享
笑着秋招😊:我一直认为努力有回报是一件很幸福很幸福的事情,恭喜你
点赞 评论 收藏
分享
评论
点赞
收藏
分享

创作者周榜

更多
牛客网
牛客网在线编程
牛客网题解
牛客企业服务