Java基礎學習——註解(Annotations)學習

註解(Annotations)是一種元數據,提供了程序之外的一些程序信息。註解並不會直接影響被註解的代碼。註解有很多用法:
1. 爲編譯器提供信息(Information for the compiler) ——編譯器能直接使用註解檢查錯誤(detect errors)和禁止警告(suppress warnings)。
2. 編譯期和部署時處理(Compile-time and deployment-time processing) —— 軟件工具可以使用註解生成代碼,XML文件等待。
3. 運行期處理(Runtime processing) —— 一些註解可以在運行期被檢測。

這篇教程主要解釋了哪些地方應該使用註解,如何應用註解,如何更好使用Java預定義註解類型;註解如何在支持插件的類型系統,寫出強類型檢測的代碼,以及如何使用重複註解(repeating annotations)。

基礎知識(Annotations Basics)

註解格式

最簡單的註解格式,看起來像下面這樣:

@Entity

標識字母(@)告訴編譯器,緊跟其後的是個註解。下面的例子裏,有個名叫Override的註解:

@Override
void mySuperMethod() { ... }

註解也可以包含元素,元素可以是命名的(named)也可以是未命名的(unnamed),這裏有些元素的值:

@Author(
   name = "Benjamin Franklin",
   date = "3/27/2003"
)
class MyClass() { ... }

或者

@SuppressWarnings(value = "unchecked")
void myMethod() { ... }

如果僅僅只有一個name爲value的元素,通常name是可以被省略的。像下面這樣:

@SuppressWarnings("unchecked")
void myMethod() { ... }

如果註解沒有元素,則括號也可以被省略,就像上面的@Override例子。

在一個聲明上,使用多個註解也是可以的。

@Author(name = "Jane Doe")
@EBook
class MyClass { ... }

如果多個註解是同一個類型,則被叫做重複註解(repeating annotation)。

@Author(name = "Jane Doe")
@Author(name = "John Smith")
class MyClass { ... }

重複註解是Java SE 8纔開始支持的。後面會有更多詳細信息。

註解類型可以是java.lang或 java.lang.annotation包裏的一個類型。在上面的例子裏,Override 和 SuppressWarnings都是預定義註解( predefined Java annotations)。但我們也可以定義自己的註解類型。上面Author和Ebook便是兩個用戶自定義註解類型。

註解的使用(Where Annotations Can Be Used)

註解可以使用在任何聲明的地方:類,方法,屬性和其他一些程序元素聲明。當聲明被註解時,按照慣例,註解通常獨佔一行。

在Java 8,註解也可以在類型上使用。這裏有一些例子:
1. new 對象的表達式(Class instance creation expression):

new @Interned MyObject();
  1. 類型轉換(Type cast)
myString = (@NonNull String) str;
  1. implements 子句(implements clause)
class UnmodifiableList<T> implements
        @Readonly List<@Readonly T> { ... }
  1. 異常拋出聲明(Thrown exception declaration)
void monitorTemperature() throws
        @Critical TemperatureException { ... }

上面的這些叫做類型註解。更多詳情,請參見Type Annotations and Pluggable Type Systems

以上內容翻譯自Java官網 註解教程

聲明註解(Declaring an Annotation Type)

代碼中,註解可以代替很多註釋。

假設有個項目,每次開始類的定義時,都需要提供關於類信息的註釋:

public class Generation3List extends Generation2List {

   // Author: John Doe
   // Date: 3/17/2002
   // Current revision: 6
   // Last modified: 4/12/2004
   // By: Jane Doe
   // Reviewers: Alice, Bill, Cindy

   // class code goes here

}

爲了使用註解來添加這些元數據(metadata),我們必須先定義註解類型。語法如下:

@interface ClassPreamble {
   String author();
   String date();
   int currentRevision() default 1;
   String lastModified() default "N/A";
   String lastModifiedBy() default "N/A";
   // Note use of array
   String[] reviewers();
}

註解的定義和接口比較相似,只是在關鍵字interface前面多了一個@。註解類型是接口(interface)的一種形式,具體內容下面會介紹。現在,我們先不用關注這些。

在上面的註解定義裏,包含了一些看起來像方法(method)的註解元素(annotation type element)的定義。注意一下,他們都可以可選地定義默認值。

在註解類型被定義之後,我們可以用真實值填充相關元素,然後使用它了。

@ClassPreamble (
   author = "John Doe",
   date = "3/17/2002",
   currentRevision = 6,
   lastModified = "4/12/2004",
   lastModifiedBy = "Jane Doe",
   // Note array notation
   reviewers = {"Alice", "Bob", "Cindy"}
)
public class Generation3List extends Generation2List {

// class code goes here

}

注意: 爲了使註解@ClassPreamble能夠出現在Javadoc自動生成的文檔裏,我們在@ClassPreamble定義時,使用註解@Documented:

// import this to use @Documented
import java.lang.annotation.*;

@Documented
@interface ClassPreamble {

   // Annotation element definitions

}

以上內容翻譯自Java官網 declaring

內置預定義註解(Predefined Annotation Types)

Java SE API裏內置了很多註解。一些註解是爲了編譯器使用的,還有一些是爲了給其他註解使用的。

Java編譯器使用的註解(Annotation Types Used by the Java Language)

在java.lang裏有@Deprecated, @Override 和 @SuppressWarnings,都屬於這一類型。

@Deprecated

@Deprecated註解表示被標記的元素時被廢棄的(deprecated),不應該再被使用。當代碼使用被@Deprecated註解的方法,屬性或者類的時候,編譯器會給出警告(warning)。當一個元素被廢棄了,它將會在Javadoc文檔裏打上@deprecated標記(@deprecated tag),就像下面的例子一樣。Javadoc註釋和註解都使用@開頭,並不是巧合,他們在概念上是相關聯的。當然,我們也會看到Javadoc tag是以小寫字母d開頭,而註解是以大寫字母D開頭。

 // Javadoc comment follows
    /**
     * @deprecated
     * explanation of why it was deprecated
     */
    @Deprecated
    static void deprecatedMethod() { }

@Override

@Override 註解告訴編譯器被註解的元素時重寫(override)父類的。關於重寫方法的信息,可以參見接口與繼承(Interfaces and Inheritance)

// mark method as a superclass method
   // that has been overridden
   @Override 
   int overriddenMethod() { }

重寫方法,並不是強制需要這個註解,但這個註解可以幫助避免錯誤。如果一個被@Override標記的方法,沒有正確重寫父類的方法,編譯器會報錯。

@SuppressWarnings

@SuppressWarnings註解告訴編譯器不要產生某個的警告。在下面的例子裏,使用了一個被廢棄(deprecated)的方法,然後編譯器會產生一個警告(warning)。在這種情況下,這個註解要會抑制編譯器產生警告。

    // use a deprecated method and tell 
    // compiler not to generate a warning
   @SuppressWarnings("deprecation")
    void useDeprecatedMethod() {
        // deprecation warning
        // - suppressed
        objectOne.deprecatedMethod();
    }

每個編譯期警告都有一個類型。Java語言規範列出了兩種類型:deprecation 和unchecked。當接口是使用泛型前的遺留代碼時,就會產生unchecked警告。爲了抑制多個類型的警告,可以使用下面語法:

@SuppressWarnings({"unchecked", "deprecation"})

@SafeVarargs @SafeVarargs註解應用於一個方法或者構造函數,表示代碼不會在可變參數(varargs)上進行不安全的操作。當@SafeVarargs註解被使用時,與可變參數相關(varargs)的unchecked警告都會被抑制。

@FunctionalInterface @FunctionalInterface註解是Java 8新加入的一個註解,用於表示類型是打算用於函數式接口的。

用於其他註解的註解(Annotations That Apply to Other Annotations)

註解用於其他的註解被叫做元註解(meta-annotations)。在java.lang.annotation裏定義了很多元註解。

@Retention

@Retention註解指明被標記的註解如何存儲:
1. RetentionPolicy.SOURCE 表示被標記的註解,僅僅只在代碼級被保留,同時會被編譯器忽略;
2. RetentionPolicy.CLASS 該標記表示被標記的註解會被編譯器保留,但僅僅在編譯期保留,但是會被JVM忽略;
3. RetentionPolicy.RUNTIME 該標記的註解,表示會被JVM保留,可以在運行時被使用。

@Documented

@Documented註解指明被註解的元素,會被Javadoc工具生成到Javadoc文檔裏(默認,註解是不會包含在Javadoc裏的)。更多信息,可以參見Javadoc工具頁

@Target

@Target註解用於註解可以用於哪些元素。一個target註解可以指定下面元素類型值的一種:
1. ElementType.ANNOTATION_TYPE 應用於一個註解類型
2. ElementType.CONSTRUCTOR 應用於構造函數
3. ElementType.FIELD 應用於類的屬性
4. ElementType.LOCAL_VARIABLE 應用於局部變量
5. ElementType.METHOD 應用於方法
6. ElementType.PACKAGE 應用於包
7. ElementType.PARAMETER 應用於方法的參數
8. ElementType.TYPE 應用於類的元素

@Inherited

@Inherited註解表示這個註解可以被從父類繼承(默認是不可以的)。當用戶判斷類是否被註解時,同時類並沒有直接被註解,這時會去查詢父類是否被註解。@Inherited註解只能在class上使用。

@Repeatable

@Repeatable註解是被Java 8新引入的,表示註解可以在同一個聲明或類型上使用多次。

以上內容翻譯自Java 官網教程 內置註解

Type註解和插件(Pluggable Type Systems)

在Java 8之前,註解僅僅只能在聲明(declarations)時使用。在Java 8,註解可以在任何 type use。這就是說,註解可以應用在任何你使用type的地方。例如,class實例的創建表達式(new),類型轉換(casts),implements子句,throws子句。這種註解被稱爲type annotation。更多例子,可以參考上面的基礎知識(Annotations Basics)。

Type annotation是爲了提高Java程序的強類型檢查功能而誕生的。Java 8並沒有提供一個類型檢查框架(type checking framework),但卻允許你自己寫(或者down)一個類型檢查框架作爲Java編譯器的插件使用。

舉個栗子,你想確保你程序裏的某些變量永遠不會被賦值爲null;你想避免產生NullPointerException。你可以自己寫個插件來檢查。你只需要修改你的代碼,把特定變量加上標識不能用null賦值的註解。這個變量的聲明可能看起來像這樣:

@NonNull String str;

當你使用NonNull模塊編譯這個代碼時,編譯器會在會檢測到潛在問題時,產生一個異常,從而提醒你修改代碼而避免這個錯誤。在你改正代碼清除所有警告後,這個特定的錯誤便不會在程序運行時產生。

你也可以使用多個檢查不同種類錯誤的類型檢查(type-checking)模塊。在這種方式下,你可以在任何時候在Java類型系統(Java type system)的任何地方添加特別的檢測。

恰當的使用類型註解和類型檢查插件,我們可以寫出更健壯和不易出錯的代碼。

在很多情況下,我們都不用自己寫類型檢查模塊。很多第三方都已經爲我們寫好了這些。例如,你可以使用華盛頓大學(University of Washington)的the Checker Framework。這個框架包含了NonNull模塊,同時還有正則表達式檢測模塊,互斥鎖模塊。更多信息,可以參見the Checker Framework的官網

以上內容翻譯自Java 官網

重複註解(Repeating Annotations)

很多時候,我們想在一個聲明或者類型上多次使用同一個註解。在Java 8中,重複註解(Repeating Annotations)便提供了我們需要的這種功能。

比如,我們編寫一個通過使用類似unix cron的定時器服務調度的方法。現在我們可以設置一個定時器來運行方法,doPeriodicCleanup,在每月月末和每個週五晚上的23:00時刻運行。爲了設置定時器,可以創建一個@Schedule註解,並且在doPeriodicCleanup方法上使用兩次。第一次表示每月月末,第二次表示每週五晚上的23:00。像下面這樣:

@Schedule(dayOfMonth="last")
@Schedule(dayOfWeek="Fri", hour="23")
public void doPeriodicCleanup() { ... }

上面的例子,是對方法進行註解。你可以對任何可以使用註解的地方進行重複註解。例如,我們有個用於處理未登陸訪問異常的class。我們可以使用@Alert註解來表示類的超級管理員和普通管理員。

@Alert(role="Manager")
@Alert(role="Administrator")
public class UnauthorizedAccessException extends SecurityException { ... }

由於兼容性的原因,重複註解(repeating annotations)存儲在一個Java編譯器自動生成的註解容器(container annotation)裏。爲了編譯器能做這些事情,在我們的代碼裏需要進行下面兩步。

步驟一:定義一個重複註解類型(Declare a Repeatable Annotation Type)

註解類型必須被元註解@Repeatable所標記。下面是個自定義重複註解類型@Schedule的定義:

import java.lang.annotation.Repeatable;

@Repeatable(Schedules.class)
public @interface Schedule {
  String dayOfMonth() default "first";
  String dayOfWeek() default "Mon";
  int hour() default 12;
}

元註解@Repeatable括號內的值,便是Java編譯器自動生成的用於存儲重複註解(repeating annotations)的註解容器(container annotation)的類型。在這個例子裏,這注解容器的類型便是Schedules。

任何沒有進行步驟一的註解,如果在同個地方使用兩次,都會產生一個編譯錯誤。

步驟二:定義註解容器類型(Declare the Containing Annotation Type)

註解容器類型必須擁有一個返回數組的value方法。Schedules類型註解容器的聲明:

public @interface Schedules {
    Schedule[] value();
}

Retrieving Annotations

在Java反射(Reflection)的API有很多方法用來獲取註解信息。有些方法返回一個註解,例如:AnnotatedElement.getAnnotationByType(Class<T>),如果提供的類型擁有註解,則會原封不動地返回。如果一個元素有多個註解,則可以通過註解容器獲得。在這種方式下,之前的遺留代碼仍然可以兼容。另外一些方法是Java SE 8新添加的,可以一次性返回多個註解,例如 AnnotatedElement.getAnnotations(Class<T>)。更多信息可以參見AnnotatedElement類

注意事項(Design Considerations)

當設計一個註解時,我們必須考慮註解的使用次數。現在註解,可以使用零次,一次或者通過@Repeatable來實現多次使用。我們也可以使用@Target來實現在哪裏註解使用的限制。例如,我們可以創建一個重複註解,只能使用在方法和屬性上。我們必須非常小心的設計自定義註解,儘可能使註解靈活和強大。

以上內容翻譯自Java 官網Repeating Annotations

問題和練習題(Questions and Exercises)

問題一

下面接口有什麼問題呢?

public interface House {
    @Deprecated
    void open();
    void openFrontDoor();
    void openBackDoor();
}

問題二

假設MyHouse類implements上面問題一的House接口

public class MyHouse implements House {
    public void open() {}
    public void openFrontDoor() {}
    public void openBackDoor() {}
}

編譯這個代碼會有一個警告,因爲open是deprecated的,如果才能避免這個警告呢

問題三

下面的代碼能編譯通過嗎?原因是什麼?

public @interface Meal { ... }

@Meal("breakfast", mainDish="cereal")
@Meal("lunch", mainDish="pizza")
@Meal("dinner", mainDish="salad")
public void evaluateDiet() { ... }

問題四

定義一個擁有四個元素id, synopsis, engineer, 和date的註解;同時,指定engineer的默認值爲unassigned,date的默認值是unknown。

答案一

上面的open函數應該指出爲啥open函數被deprecated和使用什麼函數來代替它,例如:

public interface House { 
    /**
     * @deprecated use of open 
     * is discouraged, use
     * openFrontDoor or 
     * openBackDoor instead.
     * open 函數已經Deprecated,
     * 使用openFrontDoor或
     * openBackDoor來代替它
     */
    @Deprecated
    public void open(); 
    public void openFrontDoor();
    public void openBackDoor();
}

答案二

有兩種方式,第一種,添加@Deprecated註解:

public class MyHouse implements House { 
    // The documentation is 
    // inherited from the interface.
    @Deprecated
    public void open() {} 
    public void openFrontDoor() {}
    public void openBackDoor() {}
}

另外,也可以使用@SuppressWarnings 註解:

public class MyHouse implements House { 
    @SuppressWarnings("deprecation")
    public void open() {} 
    public void openFrontDoor() {}
    public void openBackDoor() {}
}

答案三

代碼是編譯不成功的。在JDK 8之前,重複註解是不支持的。在JDK 8之後,代碼還是編譯不成功。因爲Meal註解沒有定義爲可重複的(repeatable)。可以修改Meal的定義

@java.lang.annotation.Repeatable(MealContainer.class)
public @interface Meal { ... }

public @interface MealContainer {
    Meal[] value();
}

答案四

代碼如下:

/**
 * Describes the Request-for-Enhancement (RFE) annotation type.
 */
public @interface RequestForEnhancement {
    int id();
    String synopsis();
    String engineer() default "[unassigned]";
    String date() default "[unknown]";
}

以上內容翻譯自Java 官網Questions and Exercises: Annotations

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章