Java 注解

top

现在许多主流框架都会用到 注解(Annotation) 的技术。

为了更好的理解及使用这些库,所以对注解做了整理。



注解

什么是注解

注解(Annotation) 是 Java 5 的一个新特性。注解是插入你代码中的一种注释或者说是一种元数据(meta data)。这些注解信息可以在编译期使用预编译工具进行处理(pre-compiler tools),也可以在运行期使用 Java 反射机制进行处理。


JDK 内置注解

为了便于理解,我们先看看 Java内置的注解。

JavaSE 中内置三个标准注解,定义在 java.lang 中:

  • @Override:用于修饰此方法覆盖了父类的方法;
  • @Deprecated:用于修饰已经过时的方法;
  • @SuppressWarnnings:用于通知java编译器禁止特定的编译警告。


常见示例:

@Override
public String toString() {
    return "Test Override annotation!";
}

上面示例重写了 toString() 方法,并使用了@Override注解。但是,即使我不使用@Override注解标记代码,程序也能够正常执行。@Override 注解的作用就是告诉编译器这个方法是一个重写方法(描述方法的元数据),如果父类中不存在该方法,编译器便会报错。

可以看到,这个注解非常实用,如果我们一不小心将 toString() 写成了tostring() ,那么标记这个注解时,编译器就会提醒我们写错了。


其实,注解的作用远远不仅不如此,AOP(Aspect Oriented Programming,面向切面编程) 中就大量使用注解,以此来简化代码。

JDK 中内置的注解数量和功能有限,想要更加强大的功能,必须学会自定义注解。



元注解

在学习自定义注解前,我们必须先学习 元注解


什么是元注解

元注解 就是描述注解的注解。这话有点绕,简单来说,元注解 就是用来定义注解的。


JDK 中内置的注解也是通过元注解来实现的。

例如 Override 的注解是这样定义的:

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

其中,TargetRetention 就是 元注解 ,它们共同描述了Override 的一些特性。


四种元注解

JavaSE5.0 版本在 java.lang.annotation 提供了四种元注解:

  • @Documented –注解是否将包含在 JavaDoc 中
  • @Retention –什么时候使用该注解
  • @Target –注解用于什么地方
  • @Inherited – 是否允许子类继承该注解



@Documented

一个简单的Annotations标记注解,表示是否将注解信息添加在 java 文档中。



@Retention

定义该注解的生命周期。其中有三个值可选。

  • RetentionPolicy.SOURCE

    注解仅存在于源码中,在class字节码文件中丢弃。@Override, @SuppressWarnings 都属于这类注解。

  • RetentionPolicy.CLASS

    默认的保留策略,注解会在 class 字节码文件中存在,在类加载的时候(即运行期)丢弃。

  • RetentionPolicy.RUNTIME

    运行期也保留该注解,始终不会丢弃,因此可以使用反射机制读取该注解的信息。我们自定义的注解通常使用这种方式。



@Target

定义该注解用于什么地方。如果不明确指出,该注解可以放在任何地方。

以下是一些可用的参数:

  • ElementType.TYPE -用于描述类、接口或 enum 声明
  • ElementType.FIELD -用于描述成员变量
  • ElementType.METHOD -用于描述方法
  • ElementType.PARAMETER -用于描述参数
  • ElementType.CONSTRUCTOR -用于描述构造器
  • ElementType.LOCAL_VARIABLE -用于描述局部变量
  • ElementType.ANNOTATION_TYPE -用于描述注解类型
  • ElementType.PACKAGE -用于记录java文件的package信息


可能有两个参数 ANNOTATION_TYPEPACKAGE 比较难以理解。

ANNOTATION_TYPE 表示该注解是用于描述注解的。这话很绕,举个例子,所有的 元注解 都被标注了@Target(ElementType.ANNOTATION_TYPE) 这个注解。例如,元注解@Target 本身:

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

我们说 元注解 就是描述注解的注解,它和普通注解的区别就在于 ANNOTATION_TYPE 这个参数。所有我们也可以定义元注解。


PACKAGE 表示这个注解是标注包信息,而且注解必须应用在是 package-info.java 这个文件,否则会报错的。

例如:

@Target({ElementType.PACKAGE,ElementType.METHOD})   
@Retention(RetentionPolicy.RUNTIME)   
@Documented  
public @interface Test{
    String name();
    String values();
}

需要说明的是:属性的注解是兼容的,如果你想给7个属性都添加注解,仅仅排除一个属性,那么你需要在定义target包含所有的属性。

上面的示例中, @Test 注解可以标注包,也可以标注方法。标注包时,必须新建 package-info.java 的文件

@Test (name="package", values="我在包里")
package com.deemons.annotation;

然后在使用

Package p = Package.getPackage("com.deemons.annotation");
if(p!=null && p.isAnnotationPresent(Test.class)){
    Test test = p.getAnnotation(Test.class);
    if(test !=null){
        System.out.println(test.values()+"======================="+test.name());
    }
}



@Inherited

Inherited 元注解是一个标记注解,表示是否允许子类继承该注解。

比如,一个使用了@Inherited修饰的注解被用于一个class,则这个注解将被用于该class的子类。



自定义注解

使用@interface自定义注解时,自动继承了java.lang.annotation.Annotation接口,由编译程序自动完成其他细节。在定义注解时,不能继承其他的注解或接口。

比如,最简单是注解:

public @interface MyAnnotation {
}

它没有任何元注解的描述,这个注解可以作用在任何地方。

根据前面讲的 元注解 ,你可以用 @Retention 控制它的生命周期,不声明就默认保留到字节码时期;

你可以用 @Target 控制它的作用目标,不声明就默认可以作用在任何地方;

你可以使用 @Documented 将注解添加到 文档中;

你还可以使用 @Inherited 让子类继承该注解。


定义单变量

现在这些还不够,我们看到有些注解,使用时需要添加参数。

例如,最常见的@SuppressWarnings("unchecked") ,这是因为定义该注解时添加了变量:

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

可以看到,SuppressWarnings 里面有String[] value(); 这么一行,感觉就像定义接口的方法一样样的。

虽然形式和定义接口很像,但还是有区别的。

String[] 表示注解中接收的参数类型是String 的数组。

value() 表示注解中接收的参数名称是 value。

注意:当定义单个变量,并且注解中使用的属性名为value时,对其赋值时可以不指定属性的名称而直接写上属性值接口;除了value 以外的变量名都需要使用name=value的方式赋值。

例如:

public @interface MyAnnotation {
  String type();
}

以上使用时就必须是 @MyAnnotation(type="string") 这种方式,

public @interface MyAnnotation {
  String value();
}

而这样就可以 @MyAnnotation("string") 使用了。


数组变量的使用

还有一点要说明,就是如果注解中定义的参数是数组类型,例如:

public @interface MyAnnotation {
  String[] names();
}

那么在使用时,就是这样的

@MyAnnotation(names={"a","b","c"})

当然,如果将 参数名称 names 变为 value ,则在使用时,也可以简写成

@MyAnnotation({"a","b","c"})

如果传入的数组参数的数量只有一个,那么也可以去掉大括号。


定义多变量

需要定义多个参数也简单。

public @interface MyAnnotation {
  int value();
  String[] names();
}

使用时如下:

@@MyAnnotation(value=1,names={"a","b","c"})


定义默认值

注解中的变量也是可以添加默认值的。

public @interface MyAnnotation {
  String value() default "abc";
}

如上,我们为其添加了默认值abc ,这样的话,我们在使用时,就可以不必传入参数,如果不传入参数,则使用默认值abc



代替枚举

在Android 中极力不推荐使用枚举,因为移动设备的性能有限,而枚举却是一个消耗内存的大户,因此就有了利用注解技术来代替枚举的方案。

Android 中提供了代替枚举的注解 StringDefIntDef ,注解定义在 android.support.annotation 包中:

@Retention(SOURCE)
@Target({ANNOTATION_TYPE})
public @interface StringDef {
    /** Defines the allowed constants for this element */
    String[] value() default {};
}

在使用时,如下:

    public static final String A = "a";
    public static final String B = "b";
    public static final String C = "c";

    @StringDef({A,B,C})//定义取值范围A、B、C
    public @interface Type {
    }

    @Type//限定变量取值必须在范围内
    static String sType;

    @Type//表示返回值必须是 定义范围的值
    public String getType() {
        return sType;
    }

    //表示传入的参数必须是范围内的值
    public void setType(@Type String type) {
        sType =type;
    }

后记

至此,Java 的 注解(Annotation) 也就弄清楚了,但注解其实在程序中仅仅起到标记的作用,要想真正运用注解,一般会结合反射,拿到注解的信息做对应处理。更高级的是使用注解处理器,在编译器就做处理。

接下来就来讲讲 注解处理器(Annotation Processor )

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值