彻底掌握 java 注解


注解就是java程序中一种特殊的注释,不要把注解想的那么复杂,它就是注释而已,注解本身不会对程序的代码逻辑造成任何影响。

在学java类与对象时,有关方法覆写那里,你可能会看到下面这样的代码:

class A extends B{
    @Override
    public void run(){}
}

这可能是我们最早接触到的一个注解。对,在这段代码里,@Override就是一个注解。它用来让编译器检查run()方法是否正确的进行了覆写,如果没有那就报错。

但如果我们不写@Override对代码有影响么?其实也没有。

它更多的是起到一个标注作用。比如你看到这段代码,立马就会知道,“哦,run()方法是一个覆写方法。”不仅如此,它还提供编译检查,比如@Override。

仅仅是编译检查这一点,java注解就已经与javadoc大不相同。javadoc纯粹就是一个帮助说明文档,但注解不仅仅是说明文档,它的功能大致有以下几个方面:

  1. 编译检查
  2. 生成帮助文档
  3. 使代码可读性更强
  4. 运行时获得注解信息(借助java反射机制)

接触一个新知识,一定要先搞清楚它是用来干什么的,现在你已经了解了java注解的主要用途,接下来再来慢慢的拓展更多的内容。


一、Java内置的注解

注解也是一种class,java.lang.annotation包中有一个Annotation接口,任何一个注解都要实现这个接口。这意味着我们完全可以自定义一个注解,但在此之前,需要先对java内置的注解做一个了解,同时通过介绍这些java内置的注解,理解java注解的语法和结构。

java中内置的注解就有十个,其中三个在java.lang包中,这意味着这三个注解我们是可以直接拿来用的,而不需要导入其它的包。

这三个注解是:

  • @Override :检查该方法是否是覆写方法。如果发现其父类,或者是引用的接口中并没有该方法时,会报编译错误。

  • @Deprecated :标记过时方法。如果使用该方法,会报编译警告。

  • @SuppressWarnings :指示编译器去忽略注解中声明的警告。

另外还有七个注解在java.long.annotation包下,它们分别是:

  • @Retention :用于定义注解的生命周期。

  • @Documented : 标记这些注解是否包含在用户文档中。

  • @Target :定义注解能够被应用于源码的哪些位置。

  • @Inherited :定义子类是否可以继承父类定义的注解。

  • @Repeatable :定义注解是否可以在同一个声明上使用多次。

注意到这五个注解有一个共同点,即它们的作用对象也都是注解。这意味着它们可以用来修饰其它注解,因此这些注解也被称为元注解。

除此之外,还有两个注解:

  • @SafeVarargs : 忽略任何使用参数为泛型变量的方法或构造函数调用产生的警告。

  • @FunctionalInterface :标识一个匿名函数或函数式接口。

这两个都是Java 7或者Java 8才新增的,并不是太常用,同样不太常用的还有 @Repeatable。

接下来,以这些内置注解为例,来看一看java注解是怎样实现的吧。


二、定义一个注解

在详细探讨内置注解之前,有必要首先学会怎样定义一个注解。

如下:

@Target(ElemType.Type)
@Retention(RetentionPolicy.RUNTIME)
@interface MyAnnotation{   
}

是不是感觉上面的代码很奇怪?我们说注解也是一种class,但它既不是用class 定义,也不是用 interface定义,而是用 @interface 定义。在这段代码中,我们定义了一个注解,并命名为 MyAnnotation 。注意 @interface就是定义注解的,且只能用它来定义。只有用 @interface 定义注解,才意味着它实现了java.lang.annotation.Annotation接口。

我们说,元注解是用来修饰注解的,即我们自己定义的注解。如果我们没有自定义注解,那自然用不上元注解。元注解有5个,具体是哪五个上面已经给出。在如上例子中,MyAnnotation 被两个元注解修饰,分别是 @Target 和 @Retention。

这两个元注解很重要,因为它们刚好配合 @interface 定义了一个完整的注解。什么意思呢?但凡你定义了一个注解,就必须用 @Target 和 @Retention 修饰。即使你没有明确指定,编译期也会默认加上这两个元注解。为什么会这样呢?这其实与注解的构成有关。

一个注解有三个组成部分,分别是 Annotation接口、ElementType以及RetentionPolicy。

Annotation接口好说,只要是用 @interface 定义的注解都会实现这个接口。

ElementType是指定注解能够被应用于源码的哪些位置的,这显然与 @Target 的作用是一致的。ElementType是一个枚举类,它的取值有多个,具体的取值可参照如下源码:

package java.lang.annotation;
public enum ElementType {
    TYPE,               /* 类、接口(包括注释类型)或枚举声明  */
    FIELD,              /* 字段声明(包括枚举常量)  */
    METHOD,             /* 方法声明  */
    PARAMETER,          /* 参数声明  */
    CONSTRUCTOR,        /* 构造方法声明  */
    LOCAL_VARIABLE,     /* 局部变量声明  */
    ANNOTATION_TYPE,    /* 注释类型声明  */
    PACKAGE             /* 包声明  */
}

一个注解可以指定多个ElementType。这里注意如果要指定多个ElementType的话需要向 @Target中传入一个 ElementType[] 类型。
可以参考 @Target 的源码:

package java.lang.annotation;
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Target {
    ElementType[] value();
}

RetentionPolicy也很好理解,它用来指定注解的生命周期,或者说,注解的执行策略。比如 @Override 仅在编译期有效,这意味着该注解在编译期之后就没有任何作用了。这就是注解的其中一种策略,而这些策略完全由 @Retention 指定。

可以参看@Override的源码:

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

RetentionPolicy也是一个枚举类,它有三个取值,源码参考如下:

package java.lang.annotation;
public enum RetentionPolicy {
    SOURCE,            /* Annotation信息仅存在于编译器处理期间,编译器处理完之后就没有该Annotation信息了  */
    CLASS,             /* 编译器将Annotation存储于类对应的.class文件中。默认行为  */
    RUNTIME            /* 编译器将Annotation存储于class文件中,并且可由JVM读入 */
}

接着给出@Retention的源码提供参考:

package java.lang.annotation;
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Retention {
    RetentionPolicy value();
}

注意一个注解只能有一个RetentionPolicy。

结合如上,每一个注解的构成必须满足如下:

  • 实现了Annotation接口
  • 与一个或多个ElementType相关联
  • 与一个RetentionPolicy相关联

因此,如果你需要自定义注解,那么请务必不要忘了指定 @Target 和 @Retention。即使你确实没有指定,那么 @Target 默认将应用到全部类型上,而 @Retention 将默认使用RetentionPolicy.CLASS策略。


@Target 与 @Retention说完了,再来说一说剩下的三个元注解。

#1 @Documented

@Documented用于指定注解是否可以出现在javadoc中,它默认是不出现的,如果想要它出现,那么在自定义的注解上添加上该注解即可。

不太清楚javadoc的,只需知道javadoc是指将代码注释文档化,即在归档时注释也会被编译。

它的源码如下:

package java.lang.annotation;
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Documented {
}

#2 @Inherited

@Inherited 指定子类是否可以继承父类定义的注解。在默认情况下是不被继承的。另外需要知道,继承是只有 class 和 interface 才有的概念,因此它只针对@Target(ElementType.TYPE)类型的注解有效,且只针对于class的继承,对interface的继承无效。

它的源码如下:

package java.lang.annotation;
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Inherited {
}

接下来给出一个例子:

import java.lang.annotation.*;

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@interface Inheritable{
}

@Inheritable
class InheritableFather
{
    public InheritableFather() {     
        System.out.println("InheritableFather:"+InheritableFather.class.isAnnotationPresent(Inheritable.class));
    }
}

public class InheritableSon extends InheritableFather
{
    public InheritableSon() {
        super();    
        System.out.println("InheritableSon:"+InheritableSon.class.isAnnotationPresent(Inheritable.class));
    }
    
    public static void main(String[] args)
    {
        InheritableSon is = new InheritableSon();
    }
}

运行结果:

InheritableFather:true
InheritableSon:true

在这个代码中,我们自定义了一个注解 Inheritable ,它被修饰为具有继承性。为了验证该继承性,我们特意定义了一个父类InheritableFather和其子类InheritableSon。

InheritableFather类持有Inheritable注解,且Inheritable注解被 @Inherited修饰,因此InheritableFather类的子类也将持有Inheritable注解。而运行结果显示两个结果均为true也刚好印证了我们的观点。

在输出运行结果那里,对于isAnnotationPresent()方法可能有些人会不太了解。这里多说一下。我们说,注解也是一种class,既然是class,那么自然就可以借助java的反射机制得到该注解的信息。另外,要知道,对于RetentionPolicy为SOURCE和CLASS策略的注解来说,前者在编译期就会被丢弃,而后者只会被保存进class文件中,不会加载进JVM内存中。因此这里显然是只针对于RUNTIME类型的注解的讨论。

java提供的使用反射机制读取注解的方法有:

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

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

好了,作为对照,如果将上面的示例代码中Inheritable注解的 @Inherited 去掉,那么运行结果将如下:

InheritableFather:true
InheritableSon:false

#3 @Repeateble

该注解不太常用,用途也很简单,这里不再多说。


三、java.lang包下的内置注解

元注解已经介绍完了,注解的组成与语法以及如何定义一个注解也都已讲解,不知道我说清楚了没有。接下来把java.lang包下的三个注解再做一个简短的说明。

java.lang包下有三个注解,分别是@Override、@Deprecated和@SuppressWarnings。
其中,@Override已经说过,且接触的比较多,这里不再给出示例。

#1 @Deprecated

定义源代码:

@Documented
@Retention(RetentionPolicy.RUNTIME)
public @interface Deprecated {
}

可以看到,@Deprecated并没有指定@Target,因此它是可以作用于代码的任何地方的。被@Deprecated标志的部分不再被建议使用,在Eclipse中即表现为相关的代码被打上了删除线。但不再被建议使用,其实还是可以使用,只是你最好不要这么用。因为可能用了就会出错。

我想着示例代码就不再写了吧,毕竟很好理解。


#2 @SuppressWarnings

源代码定义如下:

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

@SuppressWarnings主要起到一个编译检查的作用,它使对它所标注的内容的某些警告保持静默。

注意使用该注解需要给出参数,且参数应该为字符数组类型。因为定义了String[] value。

看一个示例:

import java.util.Date;
public class SuppressWarningTest {
    @SuppressWarnings(value={"deprecation"})
    public static void doSomething(){
        Date date = new Date(113, 8, 26);
        System.out.println(date);
    }

    public static void main(String[] args) {
        doSomething();
    }
}

这个例子编译之后无任何问题。但如果你把 @SuppressWarnings 去掉的话,在编译期会给出一个警告。原因是Data类在java中已经是不再被建议使用的类,即已经被 @Deprecated 了,但因为 @SuppressWarnings 的缘故,编译器对该警告保持了默许。

补充,@SuppressWarnings 常用的关键字表格:

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

四、给注解配置参数

我们在定义一个注解时,还可以给它配置参数。但要保证配置的参数必须是常量,因为注解要在定义后就能确定每个参数的值。因此参数的类型可以是:

  • 基本类型
  • String
  • 枚举类型
  • 上述三种类型的数组类型

参数类型可以有默认值,即如果你给注解配置了参数,但没有去用它,那么该参数将自动使用默认值。

另外,大部分注解都会有一个名为value的配置参数。

示例:

@Retention(RetentionPolicy.RUNTIME)
@Target(ELementType.FIELD)
public @interface Range {
    int min() default 0;
    int max() default 255;
}

在如上例子中,我们定义了一个Range注解,并给它配置有min和max参数,min和max参数的默认值分别是0和255。这里要提一点,我们定义的注解大都是RUNTIME注解,为什么呢?因为如果是其它策略的注解,那么定义参数什么的就没有意义了,因为不会被加载进JVM中,自然也就无法用反射来得到任何信息。

public class Text {
    @Range(min = 0,max = 999)
    public int n;
    
    @Range(max = 999) //min和value均使用默认值
    public int x;
}

这个地方有些人可能会困惑,觉得@Range注解限制了字段n或者x的值,比如以n为例,如果我们把n赋值为100000,远远超出了@Range注解中参数所指定的min-max之间的范围,那么程序会报错么?

肯定不会,在最开始我就说了,注解不会对代码逻辑造成任何影响,这里的n,只要你对它的赋值不超出int范围那就毫无问题,而与它的注解指定的参数无任何关系。那么既然毫无关系,我们还偏要写上这个参数min和max,岂不是显得毫无意义了?

当然不是,首先,注解给代码调用者一个清晰的认识,它在看到这段代码时就立马会知道,我们想要的是一个0-999之间的n。甚至我们完全可以写一个辅助函数来实现这个限制功能,如果n不是0-999之间的值就会抛出错误。

其次,即便其他人在代码运行期得到了不知名的错误,通过反射机制,他也能迅速的了解到该注解的用意然后来规避这个错误。

参考:
https://www.runoob.com/w3cnote/java-annotation.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
毕业设计,基于SpringBoot+Vue+MySQL开发的影城管理系统,源码+数据库+论文答辩+毕业论文+视频演示 随着现在网络的快速发展,网上管理系统也逐渐快速发展起来,网上管理模式很快融入到了许多生活之中,随之就产生了“小徐影城管理系统”,这样就让小徐影城管理系统更加方便简单。 对于本小徐影城管理系统的设计来说,系统开发主要是采用java语言技术,在整个系统的设计中应用MySQL数据库来完成数据存储,具体根据小徐影城管理系统的现状来进行开发的,具体根据现实的需求来实现小徐影城管理系统网络化的管理,各类信息有序地进行存储,进入小徐影城管理系统页面之后,方可开始操作主控界面,主要功能包括管理员:首页、个人中心、用户管理、电影类型管理、放映厅管理、电影信息管理、购票统计管理、系统管理、订单管理,用户前台;首页、电影信息、电影资讯、个人中心、后台管理、在线客服等功能。 本论文主要讲述了小徐影城管理系统开发背景,该系统它主要是对需求分析和功能需求做了介绍,并且对系统做了详细的测试和总结。具体从业务流程、数据库设计和系统结构等多方面的问题。望能利用先进的计算机技术和网络技术来改变目前的小徐影城管理系统状况,提高管理效率。 关键词:小徐影城管理系统;Spring Boot框架,MySQL数据库
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值