乐趣区

关于程序员:什么是-Java-注解

本文首发自「慕课网」,想理解更多 IT 干货内容,程序员圈内热闻,欢送关注!

作者 | 慕课网精英讲师 ColorfulC

通过本篇文章你将理解什么是注解,注解的作用,Java 中内置注解有哪些以及注解的分类,如何自定义注解,如何解决注解等内容。1. 什么是注解 Java 注解(Annotation)又称为 Java 标注,是 Java5 开始反对退出源代码的非凡语法元数据。Java 语言中的类、办法、变量、参数和包等都能够被标注。Java 标注能够通过反射获取标注的内容。在编译器生成 class 文件时,标注能够被嵌入到字节码中。Java 虚拟机能够保留标注内容,在运行时能够获取到标注内容。

注解是一种用于做标注的“元数据”,什么意思呢?你能够将注解了解为一个标签,这个标签能够标记类、办法、变量、参数和包。回忆咱们学习继承时,子类若重写了父类的办法,能够在子类重写的办法上应用 @Override 注解:

将 @Override 注解标注在子类重写的办法上,可查看该办法是否正确地重写了父类的办法,如有谬误将会编译报错。2. 注解的作用 2.1 内置的注解咱们先看一下 Java 提供了哪些内置注解,以及这些注解的作用。(大抵理解即可)Java 定义了一套注解,共有 10 个,5 个在 java.lang 包中,剩下 5 个在 java.lang.annotation 包中。2.1.1 用在代码的注解 @Override:查看该办法是否正确地重写了父类的办法。如果重写谬误,会报编译谬误;@Deprecated:标记过期办法。如果应用该办法,会报编译正告;@SuppressWarnings:批示编译器去疏忽注解中申明的正告;@SafeVarargs:Java 7 开始反对,疏忽任何应用参数为泛型变量的办法或结构函数调用产生的正告;@FunctionalInterface:Java 8 开始反对,标识一个匿名函数或函数式接口。2.1.2 用在其余注解的注解此类注解也称为元注解(meta annotation),在上面学习定义注解的时候,咱们将会具体解说。@Retention:标识这个注解怎么保留,是只在代码中,还是编入 class 文件中,或者是在运行时能够通过反射拜访;@Documented:标记这些注解是否蕴含在用户文档中;@Target:标记这个注解应该是哪种 Java 成员;@Inherited:标记这个注解是继承于哪个注解类;@Repeatable:Java 8 开始反对,标识某注解能够在同一个申明上应用屡次。2.2 分类 Java 注解能够分为 3 类:由编译器应用的注解:如 @Override、@Deprecated、@SupressWarnings 等;由工具解决.class 文件应用的注解:比方有些工具会在加载 class 的时候,对 class 做动静批改,实现一些非凡的性能。这类注解会被编译进入.class 文件,但加载完结后并不会存在于内存中。这类注解只被一些底层库应用,个别咱们不用本人解决;在程序运行期间可能读取的注解:它们在加载后始终存在于 JVM 中,这也是最罕用的注解。3. 定义注解学会应用注解非常简单,很多框架都会提供丰盛的注解文档(例如 Spring)。但要害的一点在于定义注解,晓得如何定义注解,才能看懂他人定义的注解。上面咱们来定义一个注解。想要定义一个注解,通常可分为 3 步:创立注解;定义注解的参数和默认值;用元注解配置注解。对于这 3 个步骤是什么意思,如何来做,咱们上面未来具体解说。3.1 创立注解注解通过 @interface 关键字来定义。例如,咱们想要定义一个可用于查看字符串长度的注解,实例如下:public @interface Length {

}
代码块 123Tips:通过 @interface 关键字定义注解,通过关键字 interface 定义接口。留神两者不要混同。在 IDEA 中,咱们能够在新建 Java 类的时候,抉择新建注解:

输出咱们要定义的注解的名称(遵循类命名标准),即可创立一个注解:

3.2 定义参数和默认值注解创立实现后,能够向注解增加一些要接管的参数,上面咱们为 @Length 注解增加 3 个参数:public @interface Length {

int min() default 0;

int max() default Integer.MAX_VALUE;

String message() default "长度不非法";

}
代码块 123456789 注解的参数相似无参数办法。另外参数的类型能够是根本数据类型、String 类型、枚举类型、Class 类型、Annotation 类型以及这些类型的数组。如果注解中只有一个参数,或者这个参数是最罕用的参数,那么应将此参数命名为 value。在调用注解时,如果参数名称是 value,且只有一个参数,那么能够省略参数名称。(因为此注解没有最罕用特色的参数,没有应用 value)能够应用 default 关键字来指定参数的默认值,举荐为每个参数都设定一个默认值。3.3 用元注解配置注解在后面学习 Java 内置的注解的时候,咱们曾经理解了元注解,元注解就是用于润饰其余注解的注解。通常只需应用这些内置元注解,就能够根本满足咱们自定义注解的需要。上面咱们将会详解 Java 内置的 5 个元注解,你将会理解为什么须要这些元注解。3.3.1 @RetentionRetention 译为保留。@Retention 注解定义了一个注解的生命周期(咱们后面对于 Java 注解的分类,就是通过其生命周期来划定界线的)。它能够有如下几种取值:RetentionPolicy.SOURCE:注解只在源码阶段保留,在编译器进行编译时它将被抛弃漠视;RetentionPolicy.CLASS:注解只被保留到编译进行的时候,它并不会被加载到 JVM 中;RetentionPolicy.RUNTIME:注解能够保留到程序运行的时候,它会被加载进入到 JVM 中,所以在程序运行时能够获取到它们。上面咱们应用 @Retention 注解来指定咱们自定义的注解 @Length 的生命周期,实例如下:import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;

@Retention(RetentionPolicy.RUNTIME)
public @interface Length {

int min() default 0;

int max() default Integer.MAX_VALUE;

String message() default "长度不非法";

}
代码块 12345678910111213 下面的代码中,咱们指定 @Length 注解能够在程序运行期间被获取到。3.3.2 @Documented 这个元注解的作用很简略,标注了此注解的注解,可能将注解中的元素蕴含到 Javadoc 中去。因而不做过多解释。3.3.3 @Target@Target 注解是最为罕用的元注解,咱们晓得注解能够被利用于类、办法、变量、参数和包等处,@Target 注解能够指定注解可能被利用于源码中的哪些地位,它能够有如下几种取值:ElementType.ANNOTATION_TYPE:能够给一个注解进行注解;ElementType.CONSTRUCTOR:能够给构造方法进行注解;ElementType.FIELD:能够给属性进行注解;ElementType.LOCAL_VARIABLE:能够给局部变量进行注解;ElementType.METHOD:能够给办法进行注解;ElementType.PACKAGE:能够给一个包进行注解;ElementType.PARAMETER:能够给一个办法内的参数进行注解;ElementType.TYPE:能够给一个类型进行注解,比方类、接口、枚举。例如,咱们定义注解 @Length 只能用在类的属性上,能够增加一个 @Target(ElementType.FIELD):import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface Length {

int min() default 0;

int max() default Integer.MAX_VALUE;

String message() default "长度不非法";

}
代码块 12345678910111213141516@Target 注解的参数也能够接管一个数组。例如,定义注解 @Length 能够用在属性或局部变量上:import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.FIELD, ElementType.LOCAL_VARIABLE})
public @interface Length {

int min() default 0;

int max() default Integer.MAX_VALUE;

String message() default "长度不非法";

}
代码块 12345678910111213141516 至此,咱们就实现了 @Length 注解的定义。上面,咱们再来看下残余的两个元注解。3.3.4 @Inherited 应用 @Inherited 定义子类是否可继承父类定义的注解。@Inherited 仅针对 @Target(ElementType.TYPE)类型的注解无效,并且仅针对类的继承无效,对接口的继承有效:@Inherited
@Target(ElementType.TYPE)
public @interface TestAnnotation {

  String value() default "test";

}
代码块 12345 在应用的时候,如果一个类用到了 @TestAnnotation:@TestAnnotation(“ 测试注解 ”)
public class Pet {
}
代码块 123 则它的子类默认也定义了该注解:public class Cat extends Pet {
}
代码块 123.3.5 @Repeatable 应用 @Repeatable 这个元注解能够定义注解是否可反复。例如,一个注解用于标注一个人的角色,他能够是学生,也能够是生存委员。@Target(ElementType.TYPE)
@Repeatable(Roles.class)
public @interface Role {

String value() default "";

}

@Target(ElementType.TYPE)
public @interface Roles {

Role[] value();

}
代码块 12345678910@Repeatable 元注解标注了 @Role。而 @Repeatable 前面括号中的类相当于一个容器注解,依照规定,它外面必须要有一个 value 的属性,属性类型是一个被 @Repeatable 注解过的注解数组。通过 @Repeatable 润饰后,在某个类型申明处,就能够增加多个 @Role 注解:@Role(“ 学生 ”)
@Role(“ 生存委员 ”)
public class Student {
}
代码块 12344. 解决注解 4.1 尝试应用注解咱们曾经实现了 @Length 注解的定义:import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.FIELD, ElementType.LOCAL_VARIABLE})
public @interface Length {

int min() default 0;

int max() default Integer.MAX_VALUE;

String message() default "长度不非法";

}
代码块 12345678910111213141516 当初就能够在字段上标注这个注解了:public class Student {

// 标注注解
@Length(min = 2, max = 5, message = "昵称的长度必须在 2~5 之间")
private String nickname;

public Student(String nickname) {this.setNickname(nickname);
}

public String getNickname() {return nickname;}

public void setNickname(String nickname) {this.nickname = nickname;}

public static void main(String[] args) {
    // 实例化对象
    Student student = new Student("我的名字很长很长");
    System.out.println(student.getNickname());
}

}

代码块 12345678910111213141516171819202122232425 下面代码中,咱们在 Student 类中的 nickname 字段上标注了 @Length 注解,限定了其长度。那么当初,是不是将其标注在字段下面就能够主动查看字段的长度了呢?答案是否定的。运行过程如下,昵称长度不非法,但并没有抛出任何异样:

Java 的注解自身对代码逻辑没有任何影响,它只是一个标注。想要查看字段的长度,就要读取注解,解决注解的参数,能够应用反射机制来解决注解。4.2 通过反射读取注解咱们先来学习一下通过反射读取注解内容相干的 API。通过反射,判断某个注解是否存在于 Class、Field、Method 或 Constructor:Class.isAnnotationPresent(Class)Field.isAnnotationPresent(Class)Method.isAnnotationPresent(Class)Constructor.isAnnotationPresent(Class)isAnnotationPresent()办法的返回值是布尔类型,例如,判断 Student 类中的 nickname 字段上,是否存在 @Length 注解:boolean isLengthPresent = Student.class.getDeclaredField(“nickname”).isAnnotationPresent(Length.class);
代码块 1 通过反射,获取 Annotation 对象:应用反射 API 读取 Annotation:Class.getAnnotation(Class)Field.getAnnotation(Class)Method.getAnnotation(Class)Constructor.getAnnotation(Class)例如,获取 nickname 字段标注的 @Length 注解:Length annotation = Student.class.getDeclaredField(“nickname”).getAnnotation(Length.class);
代码块 1 通过反射读取注解的残缺实例如下:public class Student {

// 标注注解
@Length(min = 2, max = 5, message = "昵称的长度必须在 2~6 之间")
private String nickname;

public Student(String nickname) {this.setNickname(nickname);
}

public String getNickname() {return nickname;}

public void setNickname(String nickname) {this.nickname = nickname;}

public static void main(String[] args) throws NoSuchFieldException {boolean isLengthPresent = Student.class.getDeclaredField("nickname").isAnnotationPresent(Length.class);
    if (isLengthPresent) {Length annotation = Student.class.getDeclaredField("nickname").getAnnotation(Length.class);
        // 获取注解的参数值
        int min = annotation.min();
        int max = annotation.max();
        String message = annotation.message();
        // 打印参数值
        System.out.println("min=" + min);
        System.out.println("max=" + max);
        System.out.println("message=" + message);
    } else {System.out.println("没有在 nickname 字段上找到 @Length 注解");
    }
}

}
代码块 1234567891011121314151617181920212223242526272829303132333435 运行后果:min=2
max=5
message= 昵称的长度必须在 2~6 之间
代码块 123 运行过程如下:

4.3 编写校验办法获取到了注解以及其内容,咱们就能够编写一个校验办法,来校验字段长度是否非法了。咱们在 Student 类中新增一个 checkFieldLength()办法,用于查看字段长度是否非法,如果不非法则抛出异样。残缺实例如下:import java.lang.reflect.Field;

public class Student {

// 标注注解
@Length(min = 2, max = 5, message = "昵称的长度必须在 2~5 之间")
private String nickname;

public Student(String nickname) {this.setNickname(nickname);
}

public String getNickname() {return nickname;}

public void setNickname(String nickname) {this.nickname = nickname;}

public void checkFieldLength(Student student) throws IllegalAccessException {
    // 遍历所有 Field
    for (Field field: student.getClass().getDeclaredFields()) {
        // 获取注解
        Length annotation = field.getAnnotation(Length.class);
        if (annotation != null) {
            // 获取字段
            Object o = field.get(student);
            if (o instanceof String) {String stringField = (String) o;
                if (stringField.length() < annotation.min() || stringField.length() > annotation.max()) {throw new IllegalArgumentException(field.getName() + ":" + annotation.message());
                }
            }
        }
    }
}

public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException {Student student = new Student("小");
    student.checkFieldLength(student);
}

}
代码块 12345678910111213141516171819202122232425262728293031323334353637383940414243 运行后果:Exception in thread “main” java.lang.IllegalArgumentException: nickname 昵称的长度必须在 2~5 之间

at Student.checkFieldLength(Student.java:32)
at Student.main(Student.java:41)

代码块 123 运行过程如下:

  1. 小结

通过本篇文章,咱们晓得了注解是 Java 语言的一种标注,Java 内置 10 个注解,要大抵理解每个注解的作用。应用 @interface 关键字自定义注解;为注解定义参数的时候要留神其参数的类型,举荐为每个参数都设置默认值;想要自定义注解,必须理解 Java 中内置的 5 个元注解如何应用。咱们自定义的注解通常是用于运行时读取的,因而必须应用 @Retention(RetentionPolicy.RUNTIME)进行标注。

本篇文章的概念较多且较为形象,倡议读者亲手去编写几个注解,再来浏览本篇文章会有更好的了解。

欢送关注「慕课网」,发现更多 IT 圈优质内容,分享干货常识,帮忙你成为更好的程序员!

退出移动版