Java 核心技術點之注解

vfkr1486 8年前發布 | 5K 次閱讀 Java Java開發

什么是注解

我們都知道在Java代碼中使用注釋是為了向以后閱讀這份代碼的人解釋說明一些事情,注解是注釋的升級版,它可以向編譯器、虛擬機等解釋說明一些事情。比如我們非常熟悉的@Override就是一種元注解,它的作用是告訴編譯器它所注解的方法是重寫父類的方法,這樣編譯器就會去檢查父類是否存在這個方法,以及這個方法的簽名與父類是否相同。

也就是說,注解是用來描述Java代碼的,它能夠被編譯器解析,注解處理工具在運行時也能夠解析注解。我們在Java源文件中使用注釋,是為了以后我們或他人再來讀這段代碼時,能夠更好地理解它。Javadoc工具可以解析我們在源代碼中為類、方法、變量等添加的描述信息,并根據這些描述信息自動生成一個HTML文檔,只要我們為類、方法等添加的描述信息符合Javadoc所要求的語法,我們就能夠使用Javadoc工具根據我們的描述信息自動生成一個幫助文檔。而注解比java注釋和Javadoc要強大得多,它們三者之間的重大的區別在于,Java注釋和Javadoc描述所發揮的作用僅僅到編譯時就止步了,而注解直到運行時都能夠發揮作用。

我們知道,使用“transient”關鍵字可以告訴編譯器這個域不可序列化。相比于用”transient“這樣的關鍵字修飾一個屬性,注解為我們提供了為類/方法/屬性/變量添加描述信息的更通用的方式,而這些描述信息對于開發者、自動化工具、Java編譯器和Java運行時來說都是有意義的,也就是說他們都能“讀懂”注解信息。”transient“關鍵字是一個修飾符,而 注解也是一種修飾符 。除了傳遞信息,我們也可以使用注解生成代碼。我們可以使用注解,然后讓注解解析工具來解析它們,以此來生成一些”模板化“的代碼。比如Hibernate、Spring、Axis這些框架大量使用了注解,來避免一些重復的工作。

元注解

元注解即用來描述注解的注解,比如以下代碼中我們使用“@Target”元注解來說明MethodInfo這個注解只能應用于對方法進行注解:

@Target(ElementType.METHOD)public @interface MethodInfo { 
  ...
}

下面我們來具體介紹一下幾種元注解。

Documented

當一個注解類型被@Documented元注解所描述時,那么無論在哪里使用這個注解,都會被Javadoc工具文檔化。我們來看一下它的定義:

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

我們從以上代碼中可以看到, 定義注解使用@interface關鍵字, 這就好比我們定義類時使用class關鍵字,定義接口時使用interface關鍵字一樣,注解也是一種類型。這個元注解被@Documented修飾,表示它本身也會被文檔化。@Retention元注解的值RetentionPolicy.RUNTIME表示@Documented這個注解能保留到運行時;@Target元注解的值ElementType.ANNOTATION_TYPE表示@Documented這個注解只能夠用來描述注解類型。

Inherited

表明被修飾的注解類型是自動繼承的。具體解釋如下:若一個注解類型被Inherited元注解所修飾,則當用戶在一個類聲明中查詢該注解類型時,若發現這個類聲明中不包含這個注解類型,則會自動在這個類的父類中查詢相應的注解類型,這個過程會被重復,直到該注解類型被找到或是查找完了Object類還未找到。這個元注解的定義如下:

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

我們可以看到這個元注解類型被@Documented所注解,能夠保留到運行時,只能用來描述注解類型。

Retention

我們在上面已經見到個這個元注解,它表示一個注解類型會被保留到什么時候,比如以下代碼表示Developer注解會被保留到運行時:

@Retention(RetentionPolicy.RUNTIME)
public @interface Developer { 
  String value();
}
<p>@Retention元注解的定義如下:</p>
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Retention { 
  RetentionPolicy value();
}

我們在使用@Retention時,后面括號里的內容即表示他的取值,從以上定義我們可以看到,取值的類型為RetentionPolicy,這是一個枚舉類型,它可以取以下值:

  • SOURCE:表示在編譯時這個注解會被移除,不會包含在編譯后產生的class文件中;

  • CLASS:表示這個注解會被包含在class文件中,但在運行時會被移除;

  • RUNTIME:表示這個注解會被保留到運行時,在運行時可以JVM訪問到,我們可以在運行時通過反射解析這個注解。

Target

這個元注解說明了被修飾的注解的應用范圍,也就是被修飾的注解可以用來注解哪些程序元素,它的定義如下:

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

從以上定義我們可以看到它也會保留到運行時,而且它的取值是為ElementType[]類型(一個數組,意思是可以指定多個值),ElementType是一個枚舉類型,它可以取以下值:

  • TYPE:表示可以用來注解類、接口、注解類型或枚舉類型;

  • PACKAGE:可以用來注解包;

  • PARAMETER:可以用來注解參數;

  • ANNOTATION_TYPE:可以用來注解 注解類型;

  • METHOD:可以用來注解方法;

  • FIELD:可以用來注解屬性(包括枚舉常量);

  • CONSTRUCTOR:可以用來注解構造器;

  • LOCAL_VARIABLE:可用來注解局部變量。

常見內建注解

Java本身內建了一些注解,下面我們來介紹一下我們在日常開發中比較常見的注解:@Override、@Deprecated、@SuppressWarnings。相信我們大家或多或少都使用過這三個注解,下面我們一起再重新認識一下它們。

@Override注解

我們先來看一下這個注解類型的定義:

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

從它的定義我們可以看到,這個注解可以被用來修飾方法,并且它只在編譯時有效,在編譯后的class文件中便不再存在。這個注解的作用我們大家都不陌生,那就是告訴編譯器被修飾的方法是重寫的父類的中的相同簽名的方法,編譯器會對此做出檢查,若發現父類中不存在這個方法或是存在的方法簽名不同,則會報錯。

@Deprecated

這個注解的定義如下:

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(value={CONSTRUCTOR, FIELD, LOCAL_VARIABLE, METHOD, PACKAGE, PARAMETER, TYPE})
public @interface Deprecated {
}

從它的定義我們可以知道,它會被文檔化,能夠保留到運行時,能夠修飾構造方法、屬性、局部變量、方法、包、參數、類型。這個注解的作用是告訴編譯器被修飾的程序元素已被“廢棄”,不再建議用戶使用。

@SuppressWarnings

這個注解我們也比較常用到,先來看下它的定義:

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

它能夠修飾的程序元素包括類型、屬性、方法、參數、構造器、局部變量,只能存活在源碼時,取值為String[]。它的作用是告訴編譯器忽略指定的警告信息,它可以取的值如下所示:

  • deprecation:忽略使用了廢棄的類或方法時的警告;

  • unchecked:執行了未檢查的轉換;

  • fallthrough:swich語句塊中case忘加break從而直接“落入”下一個case;

  • path:類路徑或原文件路徑等不存在;

  • serial:可序列化的類缺少serialVersionUID;

  • finally:存在不能正常執行的finally子句;

  • all:以上所有情況產生的警告均忽略。

這個注解的使用示例如下:

@SuppressWarning(value={"deprecation", "unchecked"})
public void myMethos() {
  ...
}

通過使用以上注解,我們告訴編譯器忽略myMethod方法中由于使用了廢棄的類或方法或是做了未檢查的轉換而產生的警告。

自定義注解

我們可以創建我們自己的注解類型并使用它。請看下面的示例:

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
@Inherited
public @interface MethodInfo { 
  String author() default "absfree"; 
  String date(); 
  int version() default 1;
}

在自定義注解時,有以下幾點需要我們了解:

  • 注解類型是通過”@interface“關鍵字定義的;

  • 在”注解體“中,所有的方法均沒有方法體且只允許public和abstract這兩種修飾符號(不加修飾符缺省為public),注解方法不允許有throws子句;

  • 注解方法的返回值只能為以下幾種:原始數據類型), String, Class, 枚舉類型, 注解和它們的一維數組,可以為方法指定默認返回值。

我們再把上面提到過的@SuppressWarnings這個注解類型的定義拿出來看一下,這個注解類型是系統為我們定義好的,它的定義如下:

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

我們可以看到,它只定義了一個注解方法value(),它的返回值類型為String[],沒有指定默認返回值。我們使用@SuppressWarnings這個注解所用的語法如下:

@SuppressWarnings(value={"value1", "value2", ...})

也就是在注解類型名稱后的括號內為每個注解方法指定返回值就可以使用這個注解。下面我們來看看怎么使用我們自定義的注解類型@MethodInfo:

public class AnnotationTest { 
  @MethodInfo(author="absfree", date="20160410") 
  public static void main(String[] args) { 
    System.out.println("Using custom annotation..."); 
  }
}

那么現在問題來了,我們使用的自定義注解對于編譯器或是虛擬機來說是有意義的嗎(編譯器或是虛擬機能讀懂嗎)?顯然我們什么都不做的話,編譯器或者虛擬機是讀不懂我們的自定義注解的。下面我們來介紹以下注解的解析,讓編譯器或虛擬機能夠讀懂我們的自定義注解。

注解的解析

編譯時解析

編譯時注解指的是@Retention的值為CLASS的注解,對于這類注解的解析,我們只需做以下兩件事:

  • 自定義類繼承 AbstractProcessor類;

  • 重寫其中的 process 函數。

然后編譯器在編譯時會自動查找所有繼承自 AbstractProcessor 的類,然后調用他們的 process 方法。因此我們只要做好上面兩件事,編譯器就會主動去解析我們的編譯時注解。現在,我們把上面定義的MethodInfo的Retention改為CLASS,我們就可以按照以下代碼來解析它:

@SupportedAnnotationTypes({ "com.custom.customannotation.MethodInfo" })
public class MethodInfoProcessor extends AbstractProcessor { 
  @Override 
  public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment env) { 
    HashMap<String, String> map = new HashMap<String, String>(); 
    for (TypeElement typeElement : annotations) { 
      for (Element element : env.getElementsAnnotatedWith(typeElement)) { 
        MethodInfo methodInfo = element.getAnnotation(MethodInfo.class); 
        map.put(element.getEnclosingElement().toString(), methodInfo.author()); 
      } 
    } 
    return false; 
  }
}
<p>@SupportedAnnotationTypes注解描述了Processor要解析的注解的名字。process 函數的annotations參數表示 表示待處理的注解集,env表示當前或是之前的運行環境。process函數的返回值表示annotations中的注解是否被這個Processor接受。</p>

更加詳細的介紹請參考 Android 利用 APT 技術在編譯期生成代碼

運行時注解解析

首先我們把MethodInfo注解類型中Retention的值改回原來的RUNTIME,接下來我們介紹如何通過反射機制在運行時解析我們的自定義注解類型。

java.lang.reflect包中有一個AnnotatedElement接口,這個接口定義了用于獲取注解信息的幾個方法:

//返回該程序元素的指定類型的注解,若不存在這個類型的注解則返回null
T getAnnotation(Class annotationClass)

//返回修飾該程序元素的所有注解 Annotation[] getAnnotations()

//返回直接修飾該元素的所有注解 Annotation[] getDeclaredAnnotations()

//當該程序元素被指定類型注解修飾時,返回true,否則返回false boolean isAnnotationPresent(Class<? extends Annotation> annotationClass)</code></pre>

解析我們上面的自定義注解MethodInfo的相關示例代碼如下(AnnotationParser.java):

public class AnnotationParser { 
  public static void main(String[] args) { 
    try { 
      Class cls = AnnotationTest.class; 
      for (Method method : cls.getMethods()) { 
        MethodInfo methodInfo = method.getAnnotation(MethodInfo.class); 
        if (methodInfo != null) { 
          System.out.println("method name:" + method.getName()); 
          System.out.println("method author:" + methodInfo.author()); 
          System.out.println("method date:" + methodInfo.date()); 
          System.out.println("method version:" + methodInfo.version()); 
        } 
      } 
    } catch (Exception e) { 
      e.printStackTrace(); 
    } 
  }
}

運行以上代碼我們可以得到以下輸出:

這說明我們已經成功解析了自定義注解。關于注解有點我們需要明確的是,作為描述代碼本身的一種元數據, 注解是一種”被動“的信息。也就是說,必須由編譯器或虛擬機來“主動”解析它,它才能發揮自己的作用。

參考資料

  1. Java Documention
  2. 公共技術點之Java注解
  3. Java 注解

 

 

來自:http://www.jianshu.com/p/8673bc2d5dec

 

 本文由用戶 vfkr1486 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!