小小程序员的深入浅出系列(1):Java Annotation,搞懂java注解,这一篇就够了

1 概述

开题三板斧,What(是什么) ? Why(为什么用) ? How(怎么用) ?

1.1 java annotation 是什么?

Java Annotations 可以让我们为我们的代码增加元数据(metadata),并且这些元数据可以不属于程式本身.注解是在JDK 5中加入的,且注解对于代码的执行不会有影响.

1.2 java annotation 的使用

  • 先来三个平时最经常使用的内置(Build-in)注解压惊 @Deprecated, @Override & @SuppressWarnings, 这三个注解告诉编译器需要做的工作.对,我们先来个脸熟的压压惊.
 package java.lang;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * Annotation type used to mark methods that override a method declaration in a
 * superclass. Compilers produce an error if a method annotated with @Override
 * does not actually override a method in a superclass.
 *
 * @since 1.5
 */
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.SOURCE)
public @interface Override {
}

这里是@Override的源码,现下我们仅仅看注解上的内容: 这个注解标记方法为重写了父类中的方法,编译器需要产生一个错误如果该方法没有覆盖父类中的方法.另外两个读者可以自行查询源码.

  • 通俗一点来说注解可以指导编译器的部分工作,也可以在运行时去生成代码或者xml等等,以及可以在运行时为java 反射工具提供需要的帮助,我们将在后面一步步深入,用我的潜水老师蹩脚的中文来说就是:慢慢来.

  • 此处看来java注解除开这三个常见的用法之外还只是无关痛痒的特性,但是假如您有阅读各个java 类库(比如EventBus)的源码您会发现少了如果少了注解的理解将寸步难行.

2 慢慢深入java annotation

java注解总以@符号开始,后面跟着注解的名称,如@Override ,符号@告诉编译器这是个注解.

2.1 我们能在什么地方用注解呢

java注解可以在类/接口/方法/属性上使用,例如:

@Override
void handle() { 
    //Do something 
}

这个注解告诉编译器,这个方法handle()需要覆盖父类中同样为handle()的方法.

2.2 java语言中现有的注解以及如何自定义注解

该篇的内容将会是整篇的重点,我们将从java的内置(build-in)注解以及工具包(java.lang.annotation.*)两处入手来彻底的理解注解.

2.2.1 java 内置注解

java有三个内置注解:

  • @Override
  • @Deprecated
  • @SuppressWarnings

@Override: 当需要在子类中重写父类的方法时,我们用该注解加在方法上, 从可读性上来说指出该方法是要被重写的方法,并且当不小心修改父类的方法后(修改名称或者增减参数),编译器会抛出异常.这样可以避免后续难以想象的debug灾难. 虽然还想再解释点什么但是我相信重写是每个java程序员知识体系基础中的基础就不多言了.
@Deprecated: Deprecated注解说明当前被标记的类/方法/属性是废弃的以及不应该再被使用的,编译器会为被标记的类/方法/属性生成warning .
@SuppressWarnings: 该注解告诉编译器忽略明确的警告,比如调用了上文提到的被@Deprecated标记的方法或者类.

@SuppressWarnings("deprecation")
public  void  callDeprecatedMethod() {
        a.depreactedMethod(); //调用被废弃的方法
}

看完了三个内置的注解,也许心细的读者就会有问题,为什么@Override 只能被用在方法上,@Deprecated却可以用在类/方法/属性上呢.在开始下节内容之前我们先比较一下两个注解的不同.
接下来是Override的源码:

package java.lang;
import java.lang.annotation.*;
/**
 * Indicates that a method declaration is intended to override a
 * method declaration in a supertype. If a method is annotated with
 * this annotation type compilers are required to generate an error
 * message unless at least one of the following conditions hold:
 *
 * <ul><li>
 * The method does override or implement a method declared in a
 * supertype.
 * </li><li>
 * The method has a signature that is override-equivalent to that of
 * any public method declared in {@linkplain Object}.
 * </li></ul>
 *
 * @author  Peter von der Ahé
 * @author  Joshua Bloch
 * @jls 9.6.1.4 @Override
 * @since 1.5
 */
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.SOURCE)
public @interface Override {
}

再对比下Deprecated

package java.lang;
import java.lang.annotation.*;
import static java.lang.annotation.ElementType.*;
/**
 * A program element annotated @Deprecated is one that programmers
 * are discouraged from using, typically because it is dangerous,
 * or because a better alternative exists.  Compilers warn when a
 * deprecated program element is used or overridden in non-deprecated code.
 *
 * @author  Neal Gafter
 * @since 1.5
 * @jls 9.6.3.6 @Deprecated
 */
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(value={CONSTRUCTOR, FIELD, LOCAL_VARIABLE, METHOD, PACKAGE, PARAMETER, TYPE})
public @interface Deprecated {
}

注意@Target(value={CONSTRUCTOR, FIELD, LOCAL_VARIABLE, METHOD, PACKAGE, PARAMETER, TYPE})@Target(ElementType.METHOD)的使用区别,接下来将具体讲解.

2.2.2 创建自定义注解

难点从这里开始.我们可以使用@interface 来创建注解,例如

public @interface Override {
}

注解也能有自己的属性,看起来像是方法,例如:

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.METHOD)
public @interface MyMethodAnnotation {
    String uri();
    String[] methods() default {"GET"};
}

在上面的示例代码中,我们有2个属性.还有若干的注解标签(@Target,@Retention 等等...),所有的注解都继承java.lang.annotation.Annotation,所以注解不能再继承其他的任何类.那在开始学习这些注解之前,我们先看下Annotation源码.

package java.lang.annotation;
/**
 * The common interface extended by all annotation types.  Note that an
 * interface that manually extends this one does <i>not</i> define
 * an annotation type.  Also note that this interface does not itself
 * define an annotation type.
 *
 * More information about annotation types can be found in section 9.6 of
 * <cite>The Java™ Language Specification</cite>.
 *
 * The {@link java.lang.reflect.AnnotatedElement} interface discusses
 * compatibility concerns when evolving an annotation type from being
 * non-repeatable to being repeatable.
 *
 * @author  Josh Bloch
 * @since   1.5
 */
public interface Annotation {
  
     *
     * @return true if the specified object represents an annotation
     *     that is logically equivalent to this one, otherwise false
     */
    boolean equals(Object obj);

    /**
     * Returns the hash code of this annotation, as defined below:
     * @return the hash code of this annotation
     */
    int hashCode();

    /**
     * Returns a string representation of this annotation.  The details
     * @return a string representation of this annotation
     */
    String toString();

    /**
     * Returns the annotation type of this annotation.
     * @return the annotation type of this annotation
     */
    Class<? extends Annotation> annotationType();
}

Annotation注释里第一句话就说明了Annotation是注解的父类,至于其他提供的方法equals, hashCode, toString为Object基础方法(如果有疑问可以看Effective Java中的介绍,这边不多加篇幅).接下来利用刚才定义的注解我们来做一个简单的测试:

import java.lang.annotation.Annotation;
import java.lang.reflect.Method;

class MyEasyAnnotationTest {

    public MyEasyAnnotationTest(){}
    //此数我们没有定义 methods 但是会使用default {"GET"}
    @MyAnnotation.MyMethodAnnotation(
            uri="/"
    )
    public void annotationMethods(){
        System.out.println("annotationMethods");
    }
}

public class Main {

    public static void main(String[] args) {
        Class<MyEasyAnnotationTest> clazz = MyEasyAnnotationTest.class;
        try {
            Method method  = clazz.getMethod("annotationMethods");
            MyAnnotation.MyMethodAnnotation myMethodAnnotation =  method.getAnnotation(MyAnnotation.MyMethodAnnotation.class);
            System.out.println("MyMethodAnnotation is child of Annotation ? :"+ (myMethodAnnotation instanceof Annotation));  //MyMethodAnnotation is child of Annotation ? :true
            System.out.println("uri:"+myMethodAnnotation.uri() );         //uri:/
            for (String allowedMethod : myMethodAnnotation.methods()) {
                System.out.println("allowedMethod:"+allowedMethod);       //allowedMethod:GET
            }
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        }
    }
}

输出结果符合我们之前的预期.
接下来我们挑两个常用的注解深入挖掘,此处可当笔记百科:
@Target:指定程序元定义的注释所使用的地方,它使用了另一个类:ElementType,是一个枚举类定义了注释类型可以应用到不同的程序元素以免使用者误用。看看java.lang.annotation 下的源代码:

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

ElementType是一个枚举类型,指明注释可以使用的地方,看看ElementType类:

public enum ElementType {  
     TYPE, // 指定适用点为 class, interface, enum  
     FIELD, // 指定适用点为 field  
     METHOD, // 指定适用点为 method  
     PARAMETER, // 指定适用点为 method 的 parameter  
     CONSTRUCTOR, // 指定适用点为 constructor  
     LOCAL_VARIABLE, // 指定使用点为 局部变量  
     ANNOTATION_TYPE, //指定适用点为 annotation 类型  
     PACKAGE // 指定适用点为 package  
} 

@Retention:这个元注释和java编译器处理注释的注释类型方式相关,告诉编译器在处理自定义注释类型的几种不同的选择,需要使用RetentionPolicy枚举类。此枚举类只有一个成员变量,可以不用指明成名名称而赋值,看Retention的源代码:

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

有个RetentionPolicy类,也是一个枚举类,具体看代码:

public enum RetentionPolicy {  
     SOURCE, // 编译器处理完Annotation后不存储在class中  
     CLASS, // 编译器把Annotation存储在class中,这是默认值  
     RUNTIME // 编译器把Annotation存储在class中,可以由虚拟机读取,反射需要  
} 

此处算是非常简单的写了一个介绍,自定义注解活跃在各个广受欢迎的开源库中,如Retrofit,EventBus,Struct2等等, 这篇写起来自己都觉得不够尽兴,本来想模拟Retrofit利用注解写一个简单的请求路由处理,但会导致篇幅过长,争取在下一篇内容中EventBus3.0的源码分析中再次结合java注解进行分析.感谢各位的时间!!!

参考资料:除了java.lang.annotation.*源码与Retrofit 以及EventBus源码之外还有:http://beginnersbook.com/2014/09/java-annotations/

推荐阅读更多精彩内容

  • 什么是注解(Annotation):Annotation(注解)就是Java提供了一种元程序中的元素关联任何信息和...
    九尾喵的薛定谔阅读 804评论 0 1
  • 一、什么是注解? 注解对于开发人员来讲既熟悉又陌生,熟悉是因为只要你是做开发,都会用到注解(常见的@Overrid...
    烟雨随风阅读 607评论 0 11
  • 本文章涉及代码已放到github上annotation-study 1.Annotation为何而来 What:A...
    zlcook阅读 15,552评论 12 93
  • 李敖说我不看电视 电视的毛病并非它的内容全部要不得。也不是全部庸俗讨厌。电视的毛病出在它陪你养成一个坏习惯——一个...
    伍帆阅读 44评论 0 0
  • 曾想过高考之后要去哪里看看,想过去繁华的上海在黄浦江畔看不夜城的美;想过去深圳看改革开放的壮丽,带着香港的梦幻...
    漪水盈盈阅读 208评论 7 7