【Java】【重要特性】详解注解

举报
huahua.Dr 发表于 2022/09/28 00:33:05 2022/09/28
【摘要】 一、Java注解是什么注解(Annotation),也叫元数据。一种代码级别的说明。它是JDK1.5及以后版本引入的一个特性,与类、接口、枚举是在同一个层次。它可以声明在包、类、字段、方法、局部变量、方法参数等的前面,用来对这些元素进行说明,注释。注意点:这里说的注释跟我们在代码里面使用//,/**/注释代码是不一样的,普通的代码注释会被编译器给忽略掉,而注解会被编译器打包到class文件中...

一、Java注解是什么

注解(Annotation),也叫元数据。一种代码级别的说明。它是JDK1.5及以后版本引入的一个特性,与类、接口、枚举是在同一个层次。它可以声明在包、类、字段、方法、局部变量、方法参数等的前面,用来对这些元素进行说明,注释。

注意点:这里说的注释跟我们在代码里面使用//,/**/注释代码是不一样的,普通的代码注释会被编译器给忽略掉,而注解会被编译器打包到class文件中。

二、为什么需要Java注解

从JVM的角度看,注解本身对代码逻辑没有任何影响,如何使用注解完全由工具决定。

那么我们为什么会把注解这个特性加入到Java中呢?

我们在早期没有引入Java注解之前,对我们的程序代码的增加一些配置,一般通过xml配置方式去处理,这样配置和代码隔离开来了所以XML配置更适合做一些全局的、与具体代码无关的操作,例如全局的配置等。但是如果不需要将这些配置跟代码隔离,那我们怎么处理,做一些与代码相关度高的操作,例如将Bean对应的服务暴露出去。这是就出现了注解,使用注解开发速度快,编译期间容易发现错误的出处。

三、如何使用Java注解

(1)注解的分类;Java的注解可以分为三类:

1)第一类是由编译器使用的注解,例如:

@Override:让编译器检查该方法是否正确地实现了覆写;

@SuppressWarnings:告诉编译器忽略此处代码产生的警告。

这类注解不会被编译进入.class文件,它们在编译后就被编译器扔掉了。

2)第二类是由工具处理.class文件使用的注解,比如有些工具会在加载class的时候,对class做动态修改,实现一些特殊的功能。这类注解会被编译进入.class文件,但加载结束后并不会存在于内存中。这类注解只被一些底层库使用,一般我们不必自己处理。

3)第三类是在程序运行期能够读取的注解,它们在加载后一直存在于JVM中,这也是最常用的注解。例如,一个配置了@PostConstruct的方法会在调用构造方法后自动被调用(这是Java代码读取该注解实现的功能,JVM并不会识别该注解)。

(2)注解的作用分类

1)编写文档: 通过代码里标识的元数据生成文档【生成文档doc文档】

我们要知道Java中是有三种注释的,分别为单行注释、多行注释和文档注释。而文档注释中,也有@开头的元注解,这就是基于文档注释的注解。我们可以使用javadoc命令来生成doc文档,此时我们文档的内元注解也会生成对应的文档内容。这就是编写文档的作用

2)代码分析: 通过代码里标识的元数据对代码进行分析【使用反射】

3)编译检查: 通过代码里标识的元数据让编译器能够实现基本的编译检查【Override等】

可以用来做特定的编译检查,它可以在编译期间就检查出“你是否按规定办事”,如果不按照注解规定办事的话,就会在编译期间飘红报错,并予以提示信息。可以就可以为我们代码提供了一种规范制约,避免我们后续在代码中处理太多的代码以及功能的规范。比如,@Override注解是在我们覆盖父类(父接口)方法时出现的,这证明我们覆盖方法是继承于父类(父接口)的方法,如果该方法稍加改变就会报错;@FunctionInterface注解是在编译期检查是否是函数式接口的,如果不遵循它的规范,同样也会报错。

(3)简单使用

Java的内置注解:

  • @Override: 标记在成员方法上,用于标识当前方法是重写父类(父接口)方法,编译器在对该方法进行编译时会检查是否符合重写规则,如果不符合,编译报错。
  • @Deprecated: 用于标记当前类、成员变量、成员方法或者构造方法过时如果开发者调用了被标记为过时的方法,编译器在编译期进行警告。
  • @SuppressWarnings: 压制警告注解,可放置在类和方法上,该注解的作用是阻止编译器发出某些警告信息。

定义一个注解时,还可以定义配置参数。配置参数可以包括:

  • 所有基本类型;
  • String;
  • 枚举类型;
  • 基本类型、String、Class以及枚举的数组。

因为配置参数必须是常量,所以,上述限制保证了注解在定义时就已经确定了每个参数的值。

注解的配置参数可以有默认值,缺少某个配置参数时将使用默认值。

此外,大部分注解会有一个名为value的配置参数,对此参数赋值,可以只写常量,相当于省略了value参数。如果只写注解,相当于全部使用默认值。

例如:下面@Check就是一个注解。第一个@Check(min=0, max=100, value=55)明确定义了三个参数,第二个@Check(value=99)只定义了一个value参数,它实际上和@Check(99)是完全一样的。最后一个@Check表示所有参数都使用默认值。

public class Hello {

    @Check(min=0, max=100, value=55)

    public int n;

    @Check(value=99)

    public int p;

    @Check(99) // @Check(value=99)

    public int x;

    @Check

    public int y;

}

(4)如何定义注解

Java语言使用@interface语法来定义注解(Annotation),它的格式如下:

public @interface Report {

    int type() default 0;

    String level() default "info";

    String value() default "";

}

注解的参数类似无参数方法,可以用default设定一个默认值(强烈推荐)。最常用的参数应当命名为value。

(5)元注解

有一些注解可以修饰其他注解,这些注解就称为元注解(meta annotation)。Java标准库已经定义了一些元注解,我们只需要使用元注解,通常不需要自己去编写元注解。

1)@Target元注解:使用@Target可以定义Annotation能够被应用于源码的哪些位置:

  • 类或接口:ElementType.TYPE;
  • 字段:ElementType.FIELD;
  • 方法:ElementType.METHOD;
  • 构造方法:ElementType.CONSTRUCTOR;
  • 方法参数:ElementType.PARAMETER。

定义注解@Report可用在方法或字段上,可以把@Target注解参数变为数组{ ElementType.METHOD, ElementType.FIELD }:

@Target({

    ElementType.METHOD,

    ElementType.FIELD

})

public @interface Report {

    ...

}

元注解@Target都有一个value是ElementType[]数组,只有一个元素时,可以省略数组的写法。

2)@Retention元注解:定义了Annotation的生命周期

  • 仅编译期:RetentionPolicy.SOURCE;
  • 仅class文件:RetentionPolicy.CLASS;
  • 运行期:RetentionPolicy.RUNTIME。

如果@Retention不存在,则该Annotation默认为CLASS。

@Retention(RetentionPolicy.RUNTIME)

public @interface Report {

    int type() default 0;

    String level() default "info";

    String value() default "";

}

3)@Repeatable元注解:可以定义Annotation是否可重复,使用的不多

@Repeatable(Reports.class)

@Target(ElementType.TYPE)

public @interface Report {

    int type() default 0;

    String level() default "info";

    String value() default "";

}

经过@Repeatable修饰后,在某个类型声明处,就可以添加多个@Report注解:

@Report(type=1, level="debug")

@Report(type=2, level="warning")

public class Hello {

}

4)@Inherited元注解:定义子类是否可继承父类定义的Annotation。

@Inherited仅针对@Target(ElementType.TYPE)类型的annotation有效,并且仅针对class的继承有效,对interface的继承无效:

@Inherited

@Target(ElementType.TYPE)

public @interface Report {

    int type() default 0;

    String level() default "info";

    String value() default "";

}

在使用的时候,如果一个类用到了@Report:

@Report(type=1)

public class Person {

}

则它的子类默认也定义了该注解:

public class Student extends Person {

}

(6)自定义注解(一般都是使用在RetentionPolicy.RUNTIME)与反射

因为注解定义后也是一种class,所有的注解都继承自java.lang.annotation.Annotation,因此,读取注解,需要使用反射API。

Java提供的使用反射API读取Annotation的方法包括:

1)判断某个注解是否存在于Class、Field、Method或Constructor:

  • Class.isAnnotationPresent(Class)
  • Field.isAnnotationPresent(Class)
  • Method.isAnnotationPresent(Class)
  • Constructor.isAnnotationPresent(Class)

2)使用反射API读取Annotation:

  • Class.getAnnotation(Class)
  • Field.getAnnotation(Class)
  • Method.getAnnotation(Class)
  • Constructor.getAnnotation(Class)

使用反射API读取Annotation有两种方法。

方法一是先判断Annotation是否存在,如果存在,就直接读取:

Class cls = Person.class;

if (cls.isAnnotationPresent(Report.class)) {

    Report report = cls.getAnnotation(Report.class);

    ...

}

第二种方法是直接读取Annotation,如果Annotation不存在,将返回null:

Class cls = Person.class;

Report report = cls.getAnnotation(Report.class);

if (report != null) {

   ...

}

(7)使用注解

定义了注解,本身对程序逻辑没有任何影响。我们必须自己编写代码来使用注解。这里,我们编写一个Person实例的检查方法,它可以检查Person实例的String字段长度是否满足@Range的定义:

void check(Person person) throws IllegalArgumentException, ReflectiveOperationException {

    // 遍历所有Field:

    for (Field field : person.getClass().getFields()) {

        // 获取Field定义的@Range:

        Range range = field.getAnnotation(Range.class);

        // 如果@Range存在:

        if (range != null) {

            // 获取Field的值:

            Object value = field.get(person);

            // 如果值是String:

            if (value instanceof String) {

                String s = (String) value;

                // 判断值是否满足@Range的min/max:

                if (s.length() < range.min() || s.length() > range.max()) {

                    throw new IllegalArgumentException("Invalid field: " + field.getName());

                }

            }

        }

    }

}

这样一来,我们通过@Range注解,配合check()方法,就可以完成Person实例的检查。注意检查逻辑完全是我们自己编写的,JVM不会自动给注解添加任何额外的逻辑。

 

【版权声明】本文为华为云社区用户原创内容,转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息, 否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。