Java

Java 注解

2019-08-07  本文已影响3人  wind_sky

一. 简介

注解是java5的新特性。注解可以看做一种注释或者元数据(MetaData),可以把它插入到我们的java代码中,用来描述我们的java类,从而影响java类的行为。

二. 基本语法

1. 注解的使用形式:

一个java注解由一个@符后面跟一个字符串构成,类似于这样:

@Entity

java注解中一般包含一些元素,这些元素类似于属性或者参数,可以用来设置值,比如我们有一个包含两个元素的@Entity注解:

@Entity(tableName = "vehicles", primaryKey = "id")上面注解中有两个元素,tableName和primaryKey,它们各自都被赋予了自己的元素值。

上面注解中有两个元素,tableName和primaryKey,它们各自都被赋予了自己的元素值。

2. 注解作用域:

注解可以用于描述一个类、接口、方法、方法参数、字段、局部变量等,例如

//注解一个类
@Entity
public class TestClass {
    //注解一个字段
    @Persistent
    protected String name = null;
    //注解一个方法
    @Getter
    public String getName() {
        return this.name;
    }
    //注解一个参数
    public void setName(@Optional name) {
        this.name = name;
    }

    public String testLocal(String name) {
        //注解一个局部变量
        @Optional
        List localNames = names;
        ...
    }
}
3. 声明自定义注解:

创建自定义的注解也比较简单,只需要使用 @interface 关键字即可,如

@interface MyAnnotation {}

这样就创建了一个最简单的注解。

注意,注解是不支持继承的,因此不能使用关键字extends来继承某个@interface,但注解在编译后,编译器会自动继承java.lang.annotation.Annotation接口。

4. 元注解:

所谓元注解就是标记其他注解的注解,常用的有以下几种:
1)@Target ,用来约束注解可以应用的地方,即上面提到的作用域,类、方法等。这个元注解接收的值是一个枚举类 ElementType 的数组,这个枚举类有以下值:

public enum ElementType {
    /** 标明该注解可以用于类、接口(包括注解类型)或enum声明*/
    TYPE,

    /** 标明该注解可以用于字段(域)声明,包括enum实例 */
    FIELD,

    /** 标明该注解可以用于方法声明 */
    METHOD,

    /** 标明该注解可以用于参数声明 */
    PARAMETER,

    /** 标明注解可以用于构造函数声明 */
    CONSTRUCTOR,

    /** 标明注解可以用于局部变量声明 */
    LOCAL_VARIABLE,

    /** 标明注解可以用于注解声明(应用于另一个注解上)*/
    ANNOTATION_TYPE,

    /** 标明注解可以用于包声明 */
    PACKAGE,

    /** 标明注解可以用于类型参数(泛型)声明,1.8新加入 */
    TYPE_PARAMETER,

    /** 类型使用声明,可以用于标注任何类型,但不包括Class,1.8新加入 */
    TYPE_USE
}

比如下例,声明的注解只能注解于Java 类型 和 方法

@Target({ElementType.TYPE, ElementType.METHOD})
@interface MyAnnotation {}

请注意,当注解未指定Target值时,则此注解可以用于任何元素之上。

2)@Retention ,用来规定注解的作用时机,该元注解有三种取值,

@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@interface MyAnnotation {}

上例表示MyAnnotation 注解在运行时有效,JVM会在运行时通过反射机制获取注解信息。

3)@Document ,被修饰的注解会生成到javadoc中,即当使用Java 命令生成JavaDoc 时,使用@Documented元注解定义的注解将会生成到javadoc中, 而没有此元注解的注解则不会在doc文档中出现。

4)@Inherited ,可以让注解被继承,但这并不是真的继承,只是通过使用@Inherited,可以让子类Class对象使用getAnnotations()获取父类被@Inherited修饰的注解,如

@Inherited
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.SOURCE)
@interface MyAnnotation {}

@MyAnnotation
class ParentClass {}

class ChildClass extends ParentClass {}

因为有了@Inherited 声明,所以ChildClass 也从父类ParentClass 那继承了这个注解。

5)@Repeatable ,JDK1.8新加入的,它表示在同一个位置重复相同的注解。在没有该注解前,一般是无法在同一个类型上使用相同的注解的。

//Java8前无法这样使用
@FilterPath("/web/update")
@FilterPath("/web/add")
public class A {}

下例显示了如何使用@Repeatable 元注解

@Target({ElementType.TYPE,ElementType.FIELD,ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Repeatable(FilterPaths.class)              //参数指明接收的注解class
public @interface FilterPath {
    String  value();
}

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@interface FilterPaths {
    FilterPath[] value();                   // 接收的注解必须声明的元素
}

通过使用@Repeatable后,将使用@FilterPaths注解作为接收同一个类型上重复注解的容器,而每个@FilterPath则负责保存指定的路径串。

为了处理上述的新增注解,Java8还在AnnotatedElement接口新增了getDeclaredAnnotationsByType() 和 getAnnotationsByType()两个方法并在接口给出了默认实现,在指定@Repeatable的注解时,可以通过这两个方法获取到注解相关信息。

注意,旧版API中的getDeclaredAnnotation() 和 getAnnotation() 是不对@Repeatable注解的处理的(除非该注解没有在同一个声明上重复出现)。

注意,getDeclaredAnnotationsByType方法获取到的注解不包括父类,其实当 getAnnotationsByType()方法调用时,其内部先执行了getDeclaredAnnotationsByType方法,只有当前类不存在指定注解时,getAnnotationsByType()才会继续从其父类寻找,但请注意如果@FilterPath和@FilterPaths没有使用了@Inherited的话,仍然无法获取。

5. 注解元素及其数据值:

在自定义注解中,一般都会包含一些元素以表示某些值,以便后续处理注解时使用。注解中的每个元素定义类似于接口中的方法定义,每个元素定义包含一个数据类型和名称。

@interface MyAnnotation {
    String name();
    int age();
}
 
// 使用注解,并给注解元素赋值
@MyAnnotation(name = "whx", age = 18)
public class AnnotationTest {
}

注解中的元素可以设置默认值,通过default 来实现

@interface MyAnnotation {
    String name() default "whx";
    int age() default 18;
}

当一个元素被设置默认值之后,这个元素便成了注解的可选元素,即在使用注解时如果不为这个元素赋值,将使用默认值。

编译器对元素的默认值有一些限制,首先,元素不能有不确定的值,也就是说,元素必须要么具有默认值,要么在使用注解时提供元素的值。其次,对于非基本类型的元素,无论是在源代码中声明,还是在注解接口中定义默认值,都不能以null作为值,因为每个注解的声明中,所有的元素都存在,并且都具有相应的值。

注解元素的数据类型:

倘若使用了其他数据类型,编译器将会丢出一个编译错误,注意,声明注解元素时可以使用基本类型但不允许使用任何包装类型,同时还应该注意到注解也可以作为元素的类型,也就是嵌套注解,如下例

@interface MyAnnotation {
    enum Gender {MALE, FEMALE}
    
    // 枚举类型
    Gender status() default Gender.MALE;

    // 常用类型
    String name() default "whx";
    int age() default 18;

    // Class 类型
    Class<?> testCase() default Void.class;

    // 注解嵌套
    Reference reference() default @Reference(next = true);
    
    // 数组类型
    long[] value() default {1, 2, 3};
}
 
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@interface Reference {
    boolean next() default false;
}

快捷方式:

就是注解中定义了名为value 的元素,并且在使用该注解时,如果该元素是唯一需要赋值的一个元素,那么此时无需使用key=value的语法,而只需在括号内给出value元素所需的值即可。这可以应用于任何合法类型的元素,但是,这限制了元素名必须为value 。

@interface QuickWay {
    String value();
}

@QuickWay("hello world")
class QuickTest { 
}
6. 常用Java 内置注解:

主要有三个:

三. 注解与反射机制

上面对注解做了一个详细介绍,具体该如何使用我们的自定义注解呢?其实在现实应用中,我们的自定义注解一般都是起到运行时指示的作用,也就是运行时注解。对于运行时注解,我们可以通过反射机制获得注解信息。

Java在java.lang.reflect 反射包下新增了AnnotatedElement接口,它主要用于表示目前正在 VM 中运行的程序中已使用注解的元素,通过该接口提供的方法可以利用反射技术地读取注解的信息,如反射包的Constructor类、Field类、Method类、Package类和Class类都实现了AnnotatedElement接口。

下面是AnnotatedElement中相关的API方法:

返回值 方法名称 说明
<A extends Annotation> getAnnotation(Class<A> annotationClass) 该元素如果存在指定类型的注解,则返回这些注解,否则返回 null。
Annotation[] getAnnotations() 返回此元素上存在的所有注解,包括从父类继承的
boolean isAnnotationPresent(Class<? extends Annotation> annotationClass) 如果指定类型的注解存在于此元素上,则返回 true,否则返回 false。
Annotation[] getDeclaredAnnotations() 返回直接存在于此元素上的所有注解,注意,不包括父类的注解,调用者可以随意修改返回的数组;这不会对其他调用者返回的数组产生任何影响,没有则返回长度为0的数组

下面来看几个示例:

class ATest {
    public static void main(String[] args) {

        Class<?> clazz = ChildClass.class;

        // 判断类上是否有指定的注解
        boolean b = clazz.isAnnotationPresent(CommonAnno.class);

        // 根据指定注解类型获取该注解
        ClassAnno classAnno = clazz.getAnnotation(ClassAnno.class);
        System.out.println(classAnno.name());

        // 获取该类上的所有注解,包括从父类继承
        Annotation[] anos = clazz.getAnnotations();
        System.out.println(Arrays.toString(anos));

        // 获取该类上的所有注解,不包括从父类继承
        Annotation[] anos2 = clazz.getDeclaredAnnotations();
        System.out.println(Arrays.toString(anos2));
 
        try {
            // 获取方法上的注解
            Method method = clazz.getMethod("add", int.class, int.class);
            Annotation[] ans = method.getAnnotations();
            System.out.println(Arrays.toString(ans));
 
            // 获取方法参数的注解
            Annotation[][] paramAnnos = method.getParameterAnnotations();
            Class[] paramTypes = method.getParameterTypes();

            int i = 0;

            for (Annotation[] annotations : paramAnnos) {
                Class paramType = paramTypes[i++];
                for (Annotation anno : annotations) {
                    if (anno instanceof ParamAnno) {
                        ParamAnno paramAnno = (ParamAnno) anno;

                        System.out.println(paramType.getName());
                        System.out.println(paramAnno.name());
                    }
                }
            }
 
            // 获取属性的注解
            Field field = clazz.getField("dep");
            FiledAnno anno = field.getAnnotation(FiledAnno.class);
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        }       
    }
}

上面只是一些简单的示例,可以通过结合注解和反射实现一些复杂的功能,比如Spring 框架的基于注解的配置等。

上一篇 下一篇

猜你喜欢

热点阅读