java注解

前言

初学 Java 时,注解这部分知识是匆匆略过的,后来开发 Android 过程中遇到 butterknife 等跟注解有关的代码,也只是按照 API 调用,没有深入研究。前几天跟 web 端同事联调时,发现他们代码中用到注解的地方挺多,方法体上方加一行简短的代码,就能实现复杂的业务逻辑。所以今天认真研究一下注解的用法和特性,弥补知识漏洞。

Java 中的注解

Annotation 是 Java 1.5 中引入的,用来描述 Java 代码的元数据。那什么是元数据呢?下面是wiki的解释:

元资料Metadata),又称元数据诠释资料中介资料中继资料后设资料等,为描述其他资料资讯的资料[1]。有三种不同类型的元资料,分别是记叙性元资料结构性元资料管理性元资料[2]

好吧,看了跟没看一样。我个人觉得 annotation 从形式上看类似于注释,可以作用于某个类、方法、变量、参数等,有解释说明某段代码的作用,但它的功能比注释强大得多。

####注解的作用

  • 标记,用于告诉编译器某些信息
  • 编译时动态处理,如动态生成代码
  • 运行时动态处理,如通过反射获取注解

注解的分类

  • 内置注解(标准注解)

    Java 提供了三个内置注解,如下所示,都是最常见的

    • @Deprecated :表示java不赞成使用这些被描述的对象
    • @Override :用来修饰对父类进行重写的方法。如果父类中的方法名称或参数发生改变时,如果子类没有做相应的调整编译器便会报错
    • @SuppressWarnings :使编译器忽略掉编译器警告,比如使用了 Deprecated 的类或方法
  • 元注解

    元注解就是描述注解的注解,我们可以通过元注解来控制自定义注解的行为

    @Documented 标志将此注解包含至 javadoc 中

    @Retention 定义注解保存级别

    • SOURCE:源码时注解,被编译器丢弃
    • CLASS :编译时,被编译器记录在class文件中,运行时被VM丢弃,在编译时使用,属于默认配置
    • RUNTIME:运行时, 被编译器记录在class文件中,运行时可用,被VM保留,所以可以在运行时搭配反射使用

    @Target 定义注解适用的目标

    • TYPE:Class类,接口(包括注解类型或者enum类型)
    • FIELD:属性(包括enum实例)
    • METHOD:方法
    • PARAMETER:方法参数
    • CONSTRUCTOR:类构造器
    • LOCAL_VARIABLE:本地变量
    • ANNOTATION_TYPE:注解
    • PACKAGE:包

    @Inherited 让一个类和它的子类都包含某个注解

  • 自定义注解

    注解的定义类似于接口,不过注解是 @interface

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    @Documented
    @Retention(RetentionPolicy.RUNTIME)
    @Target(ElementType.TYPE)
    @Inherited
    public @interface MyAnnotation {
    String value() default "xxx";
    String name();
    int age();
    String[] newNames();
    }

    a. 所有方法没有方法体,没有参数没有修饰符,实际只允许 public & abstract 修饰符,默认为 public ,不允许抛异常

    b. ⽅法返回值只能是基本类型,String, Class,annotation,enumeration 或者是它们的一维数组

    c. 若只有⼀个默认属性,可直接⽤用 value() 函数。一个属性都没有表⽰示该 Annotation 为 Mark Annotation

    d.可以加 default 表示默认值

    下面这段代码演示了如何使用该注解:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    @MyAnnotation(
    value="123"(value有默认值了,这里可以不指定),
    name="Jakob",
    age=37,
    newNames={"Jenkov", "Peterson"}
    )
    public class MyClass {
    }

    我们需要为所有的注解元素设值,但有默认值的元素,我们可以不设值。

通过反射获取注解中的信息

​ 对于运行时注解,我们可以通过反射机制获得注解信息,以上面的注解为例。

  • 获取类的注解信息

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    public class TestAnnotation {
    public static void main(String[] args) {
    //通过反射获得MyClass的注解信息
    MyAnnotation myAnnotation = MyClass.class.getAnnotation(MyAnnotation.class);
    System.out.println(myAnnotation.name());
    System.out.println(myAnnotation.value());
    ······
    }
    }
  • 获取方法的注解信息

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    public class TestAnnotation {
    public static void main(String[] args) {
    Method method = null;
    try {
    method = MyClassB.class.getMethod("method");
    Annotation annotation = method.getAnnotation(MyAnnotation.class);
    if (annotation !=null) {
    MyAnnotation myAnnotation = (MyAnnotation) annotation;
    System.out.println("name: " + myAnnotation.name());
    System.out.println("value: " + myAnnotation.value());
    }
    } catch (NoSuchMethodException e) {
    e.printStackTrace();
    }
    }
    }
  • 获取方法参数的注解信息

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    Method method = MyClassC.class.getMethod("method")
    Annotation[][] parameterAnnotations = method.getParameterAnnotations();
    Class[] parameterTypes = method.getParameterTypes();
    int i=0;
    for(Annotation[] annotations : parameterAnnotations){
    Class parameterType = parameterTypes[i++];
    for(Annotation annotation : annotations){
    if(annotation instanceof MyAnnotation){
    MyAnnotation myAnnotation = (MyAnnotation) annotation;
    System.out.println("param: " + parameterType.getName());
    System.out.println("name : " + myAnnotation.name());
    System.out.println("value: " + myAnnotation.value());
    }
    }
    }

    请注意Annotation[][] annos = method.getParameterAnnotations(),这得到的是一个二维数组,它是怎么组合的呢 ?举个例子

    1
    public void method(@MyAnnotation1()int id,@MyAnnotation2()String name){···}

    第一个参数的下标是0,第二个参数的下标是1,那么

    annos[0][0] = MyAnnotation1,annos[1][0] = MyAnnotation2,参数前可以添加多个注解,所以得到的是个二维数组。

  • 获取变量的注解信息

    1
    2
    3
    4
    5
    6
    7
    8
    Field field = MyClassB.class.getField("field");
    Annotation annotation = field.getAnnotation(MyAnnotation.class);
    if(annotation instanceof MyAnnotation){
    MyAnnotation myAnnotation = (MyAnnotation) annotation;
    System.out.println("name: " + myAnnotation.name());
    System.out.println("value: " + myAnnotation.value());
    }

Android 中的注解

关于 Android 中常用的注解,技术小黑屋有一篇文章介绍的很详细(传送门),我就不写了。现在特别火的Android开源框架,基本都有自定义注解的影子,如ButterKnife、EventBus、Retrofit等。我会另外写博客分析常用开源框架的源码,敬请期待。

以上内容借鉴了很多大神的文章,十分感谢:

Java注解详解

详解Java中的注解

java annotation 详解

0%