美文网首页MacOS, Java和IDEAJava 杂谈
Java注解Annotation快速入门

Java注解Annotation快速入门

作者: SpaceCat | 来源:发表于2019-02-26 00:04 被阅读0次

    注解,Annotation,是Java语言5.0版本引入的特性。自诞生到现在,Java语言的项目中,注解出现的频率比较高,已经成为了Java语言非常重要的特性之一。
    这篇就是将注解的快速上手。本文假定读者在java代码中见过注解,但是,对注解一知半解,一无所知。

    1、什么是注解

    Annotations, a form of metadata, provide data about a program that is not part of the program itself. Annotations have no direct effect on the operation of the code they annotate.
    From https://docs.oracle.com/javase/tutorial/java/annotations/

    注解,是一种元数据,提供一些和程序相关的信息(这些信息又不是程序的一部分)。注解不会对所注解的程序产生直接影响。

    2、注解的作用

    Annotations have a number of uses, among them:
    (1) Information for the compiler — Annotations can be used by the compiler to detect errors or suppress warnings.
    (2) Compile-time and deployment-time processing — Software tools can process annotation information to generate code, XML files, and so forth.
    (3) Runtime processing — Some annotations are available to be examined at runtime.
    From https://docs.oracle.com/javase/tutorial/java/annotations/

    注解有好多作用,包括但不限于:

    • 为编译器提供信息,使编译器能够检测错误或者抑制警告。
    • 辅助编译期和部署期处理。一些软件工具能够处理注解产生代码、XML文件等。
    • 运行期处理。有些注解能够在程序运行时被检测到。

    3、注解快速上手实例

    上面两部分均来自于Oracle官网,可能读了之后,仍然对注解一知半解,一无所知。接下来,直接用一个例子上手让大家了解注解。
    例子中,定义了一个Player类,其中有好多成员变量,比如name、team等。现在,我们通过注解,实现这样一个功能,就是为每一个Player的成员变量添加一条打印时的注释,比如team="Lakers",在打印时打印出Contribute for Lakers。这样,我们在重写Player的toString方法时,能够通过获取成员变量注解的值,来实现出可读性更好的toString方法。
    首先,我们需要定义该注解:
    PrintComment.java

    import java.lang.annotation.Retention;
    import java.lang.annotation.RetentionPolicy;
    
    /**
     * Created by chengxia on 2019/2/24.
     */
    @Retention(RetentionPolicy.RUNTIME)
    public @interface PrintComment {
        int length();
        String comment();
    }
    

    从这里看出,注解,实际上和接口的定义非常相似,只不过,多了一个@符号。需要注意的是,注解中的成员变量定义的最后都有()。上面定义了名为PrintComment的注解,其中有两个成员变量,int类型的length和String类型的comment。PrintComment注解的前面还有一个元注解@Retention(RetentionPolicy.RUNTIME),用来说明该注解是用在程序运行时的注解。
    然后,我们在程序中使用注解:
    Player.java

    import java.lang.reflect.Field;
    
    /**
     * Created by chengxia on 2019/2/24.
     */
    public class Player {
    
        //@PrintComment(length=6, comment = "Name: ")
        private String name;
    
        @PrintComment(length=14, comment = "Chinese Name: ")
        private String chineseName;
    
        @PrintComment(length=15, comment = "Contribute for ")
        private String team;
    
        @PrintComment(length=10, comment = "Birth on: ")
        private String birthday;
    
        public Player(String name, String chineseName, String team, String birthday) {
            this.name = name;
            this.team = team;
            this.birthday = birthday;
            this.chineseName = chineseName;
        }
    
        public String getName() {
            return name;
        }
    
        public void setName(String name) {
            this.name = name;
        }
    
        public String getTeam() {
            return team;
        }
    
        public void setTeam(String team) {
            this.team = team;
        }
    
        public String getBirthday() {
            return birthday;
        }
    
        public void setBirthday(String birthday) {
            this.birthday = birthday;
        }
    
        @Override
        public String toString(){
            StringBuffer resStrBuf = new StringBuffer();
            try{
                //Class clazz = Class.forName("Player");//根据类名获得其对应的Class对象,注意是全名。如果有包的话要加上,比如java.Lang.String
                Class clazz = this.getClass();//根据实例获得Class对象
                Field[] fields = clazz.getDeclaredFields();//根据Class对象获得属性 私有的也可以获得
                //遍历该类的每一个成员变量
                for(Field f : fields) {
                    //f.setAccessible(true);
                    //获得该成员变量的注解
                    PrintComment annotation = f.getAnnotation(PrintComment.class);
    
                    if(annotation != null) {//如果该成员变量上有注解,打印时添加打印注释。
                        //获得该成员变量的值,并结合注解上的值,一并输出
                        resStrBuf.append(annotation.comment() + f.get(this) + "\n");
    //                System.out.println(f.getType().getName());//打印每个属性的类型名字
    //                System.out.println(f.getName());//打印每个属性的类型名字
                    }else{//如果该成员变量上无注解,直接打印。
                        resStrBuf.append(f.get(this) + "\n");
                    }
                }
            }catch (Exception e){
                e.printStackTrace();
            }
            return resStrBuf.toString();
        }
    
        public static void main(String []args){
            Player p = new Player("Tim Duncan","蒂姆·邓肯","San Antonio Spurs","1976.4.25");
            System.out.println(p);
        }
    }
    

    运行结果,如下图:


    Hello Annotation!

    上面的例子中,就完成了一条自定义的运行时注解的使用。

    4、注解的语法

    4.1 注解的定义

    就像上面的例子中,看到的那样,注解通过@interface关键字来定义,很像是接口:

    @Retention(RetentionPolicy.RUNTIME)
    public @interface PrintComment {
        int length();
        String comment();
    }
    

    定义注解的时候,也会用到元注解,上面的例子中,用到了@Retention元注解用来说明这个注解是一个运行时的注解。
    在注解中一般会有一些成员元素以表示某些值。注解的元素看起来就像接口的方法,唯一的区别在于可以为其制定默认值。没有元素的注解称为标记注解,下面的@Test就是一个标记注解:

    @Retention(RetentionPolicy.RUNTIME)
    public @interface Test {
    }
    

    注解成员元素的类型包括以下几种:所有基本类型、String、Class、enum、Annotation、以上类型的数组形式。元素不能有不确定的值,即要么有默认值,要么在使用注解的时候提供元素的值。而且元素不能使用null作为默认值。注解在只有一个元素且该元素的名称是value的情况下,在使用注解的时候可以省略“value=”,直接写需要的值即可。从这点可以看出,上面用到的元注解@Retention肯定是一个只有一个元素的注解。

    4.2 元注解

    除了上面用到的@Retention元注解,还有其种类的元注解。JDK 5.0中定义了4个标准的meta-annotation类型,它们在定义注解时被使用,用来说明所定义注解的信息。如下:

    • @Target
    • @Retention
    • @Documented
    • @Inherited
      这些元注解的定义可以在java.lang.annotation包中找到。

    4.2.1 @Target

    Annotation可被用于 packages、types(类、接口、枚举、Annotation类型)、类型成员(方法、构造方法、成员变量、枚举值)、方法参数和本地变量(如循环变量、catch参数)。
    @Target说明了Annotation所修饰的对象范围,即当前所定义注解的使用范围。在Annotation类型的声明中使用了target可更加明晰其修饰的目标。
    取值(ElementType)有:

    • CONSTRUCTOR:用于描述构造器
    • FIELD:用于描述域
    • LOCAL_VARIABLE:用于描述局部变量
    • METHOD:用于描述方法
    • PACKAGE:用于描述包
    • PARAMETER:用于描述参数
    • TYPE:用于描述类、接口(包括注解类型) 或enum声明
      如下就定义了一个只能修饰方法的TestAnno注解:
    @Target(ElementType.METHOD)
    public @interface TestAnno {
        String value();
    }
    

    4.2.2 @Retention

    @Retention表示需要在什么级别保存该注解信息,用于描述注解的生命周期(即:被描述的注解在什么范围内有效)。
    @Retention定义当前Annotation被保留的时间长短:某些Annotation仅出现在源代码中,而被编译器丢弃;而另一些却被编译在class文件中;编译在class文件中的Annotation可能会被虚拟机忽略,而另一些在class被装载时将被读取(请注意并不影响class的执行,因为Annotation与class在使用上是被分离的)。使用这个meta-Annotation可以对 Annotation的“生命周期”限制。
    Retention元注解有唯一的value作为成员,它的取值来自java.lang.annotation.RetentionPolicy的枚举类型值。取值(RetentionPoicy)有:

    • SOURCE:在源文件中有效(即源文件保留)
    • CLASS:在class文件中有效(即class保留)
    • RUNTIME:在运行时有效(即运行时保留)

    4.2.3 @Documented

    Documented也位于java.lang.annotation包下,只用于修饰注解。
    被Documented修饰的注解A,然后使用注解A去修饰某个类B(B也可以是方法或者属性),在使用javadoc命令将B生成API帮助文档时,将会把注解信息也显示在此文档中。
    参考:javaAPI元注解之Documented

    4.2.4 @Inherited

    @Inherited 元注解是一个标记注解。如果一个使用了@Inherited修饰的annotation类型被用于一个class,则该class的子类,也将自动继承该注解。
    注意:@Inherited annotation类型是被标注过的class的子类所继承。类并不从它所实现的接口继承annotation,方法并不从它所重载的方法继承annotation。
    当@Inherited修饰的注解,也被@Retention(RetentionPolicy.RUNTIME)修饰时,则反射API增强了这种继承性。如果我们使用java.lang.reflect去查询一个@Inherited annotation类型的annotation时,反射代码检查将展开工作:检查class和其父类,直到发现指定的annotation类型被发现,或者到达类继承结构的顶层。

    4.2.5 @Repeatable

    @Repeatable是JDK 1.8中引入了一个元注解。
    Repeatable是可重复的意思。@Repeatable元注解用于标识当前定义的注解可以多次被使用,也就是注解的值可以同时取多个。

    @interface Persons {
        Person[]  value();
    }
    
    @Repeatable(Persons.class)
    @interface Person{
        String role default "";
    }
    
    @Person(role="artist")
    @Person(role="coder")
    @Person(role="PM")
    public class SuperMan{
        
    }
    

    上面的代码,@Repeatable 注解了 Person。而 @Repeatable 后面括号中的类相当于一个容器注解,也就是用来存放其它注解的地方。它本身也是一个注解:

    @interface Persons {
        Person[]  value();
    }
    

    按照规定,它里面必须要有一个value的属性,属性类型是一个被@Repeatable注解过的注解数组(注意:它是数组)。
    在获取@Repeatable的注解时,也有专门的api支持。参考:秒懂,Java 注解 (Annotation)你可以这样学

    5、JDK自带的原生注解

    在java.lang包下,JAVA提供了5个自带的原生注解。

    5.1 @Override

    限定重写父类方法。对于子类中被@Override 修饰的方法,如果存在对应的被重写的父类方法,则正确;如果不存在,则报错。@Override 只能作用于方法,不能作用于其他程序元素。

    5.2 @Deprecated

    用于表示某个程序元素(类、方法等)已过时。如果使用了被@Deprecated修饰的类或方法等,编译器会发出警告。

    5.3 @SuppressWarnings

    抑制编译器警告。指示被@SuppressWarnings修饰的程序元素(以及该程序元素中的所有子元素,例如类以及该类中的方法.....)取消显示指定的编译器警告。例如,常见的@SuppressWarnings(value="unchecked")。
    SuppressWarnings注解的常见参数值的简单说明:

    • deprecation:使用了不赞成使用的类或方法时的警告(使用@Deprecated使得编译器产生的警告);
    • unchecked:执行了未检查的转换时的警告,例如当使用集合时没有用泛型 (Generics) 来指定集合保存的类型; 关闭编译器警告
    • fallthrough:当 Switch 程序块直接通往下一种情况而没有 Break 时的警告;
    • path:在类路径、源文件路径等中有不存在的路径时的警告;
    • serial:当在可序列化的类上缺少 serialVersionUID 定义时的警告;
    • finally:任何 finally 子句不能正常完成时的警告;
    • all:关于以上所有情况的警告。

    5.4 @SafeVarargs

    @SafeVarargs是JDK 7 专门为抑制“堆污染”警告提供的。

    5.5 @FunctionalIterface

    java 8 新增的,函数式接口。Java8规定:如果接口中只有一个抽象方法(可以包含多个默认方法或多个static方法),该接口称为函数式接口。
    @FunctionalInterface就是用来指定某个接口必须是函数式接口,否则就会编译出错。

    @FunctionalInterface
    public interface Fun
    {
        static void foo()
        {
            System.out.println("foo类方法");
        }
        default void bar()
        {
            System.out.println("bar默认方法");
        }
        void test();//只定义了一个抽象方法
    }
    

    如在上面的接口中再加一个抽象方法abc(),则会编译出错。

    参考资料

    相关文章

      网友评论

        本文标题:Java注解Annotation快速入门

        本文链接:https://www.haomeiwen.com/subject/jzcoyqtx.html