讀古今文學網 > Java 8實戰 > 附錄 A 其他語言特性的更新 >

附錄 A 其他語言特性的更新

本附錄中,我們會討論Java 8中其他的三個語言特性的更新,分別是重複註解(repeated annotation)、類型註解(type annotation)和通用目標類型推斷(generalized target-type inference)。附錄B會討論Java 8中類庫的更新。我們不會涉及JDK 8中的所有內容,比如我們不會談及Nashorn或者是精簡運行時(Compact Profiles),因為它們屬於JVM的新特性。本書專注於介紹類庫和語言的更新。如果你對Nashorm或者精簡運行時感興趣,我們推薦你閱讀以下兩個鏈接的內容,分別是http://openjdk.java.net/projects/nashorn/和http://openjdk.java.net/jeps/161。

A.1 註解

Java 8在兩個方面對註解機制進行了改進,分別為:

  • 你現在可以定義重複註解

  • 使用新版Java,你可以為任何類型添加註解

正式開始介紹之前,我們先快速地回顧一下Java 8之前的版本能用註解做什麼,這有助於我們加深對新特性的理解。

Java中的註解是一種對程序元素進行配置,提供附加信息的機制(注意,在Java 8之前,只有聲明可以被註解)。換句話說,它是某種形式的語法元數據(syntactic metadata)。比如,註解在JUnit框架中就使用得非常頻繁。下面這段代碼中,setUp方法使用了@Before進行註解,而testAlgorithm使用了@Test進行註解:

@Before
public void setUp{
    this.list = new ArrayList<>;
}

@Test
public void testAlgorithm{
     ...
    assertEquals(5, list.size);
}

  

註解尤其適用於下面這些場景。

  • 在JUnit的上下文中,使用註解能幫助區分哪些方法用於單元測試,哪些用於做環境搭建工作。

  • 註解可以用於文檔編製。比如,@Deprecated註解被廣泛應用於說明某個方法不再推薦使用。

  • Java編譯器還可以依據註解檢查錯誤,禁止報警輸出,甚至還能生成代碼。

  • 註解在Java企業版中尤其流行,它們經常用於配置企業應用程序。

A.1.1 重複註解

之前版本的Java禁止對同樣的註解類型聲明多次。由於這個原因,下面的第二句代碼是無效的:

@interface Author { String name; }

@Author(name="Raoul") @Author(name="Mario") @Author(name="Alan")    ←─錯誤:重複的註解
class Book{ }

  

Java企業版的程序員經常通過一些慣用法繞過這一限制。你可以聲明一個新的註解,它包含了你希望重複的註解數組。這種方法的形式如下:

@interface Author { String name; }
@interface Authors {
    Author value;
}

@Authors(
  { @Author(name="Raoul"), @Author(name="Mario") , @Author(name="Alan")}
)
class Book{}

  

Book類的嵌套註解相當難看。這就是Java 8想要從根本上移除這一限制的原因,去掉這一限制後,代碼的可讀性會好很多。現在,如果你的配置允許重複註解,你可以毫無顧慮地一次聲明多個同一種類型的註解。它目前還不是默認行為,你需要顯式地要求進行重複註解。

創建一個重複註解

如果一個註解在設計之初就是可重複的,你可以直接使用它。但是,如果你提供的註解是為用戶提供的,那麼就需要做一些工作,說明該註解可以重複。下面是你需要執行的兩個步驟:

(1) 將註解標記為@Repeatable

(2) 提供一個註解的容器

下面的例子展示了如何將@Author註解修改為可重複註解:

@Repeatable(Authors.class)
@interface Author { String name; }
@interface Authors {
    Author value;
}

  

完成了這樣的定義之後,Book類可以通過多個@Author註解進行註釋,如下所示:

@Author(name="Raoul") @Author(name="Mario") @Author(name="Alan")
class Book{ }

  

編譯時,Book會被認為使用了@Authors({@Author(name="Raoul"), @Author(name ="Mario"), @Author(name="Alan")})這樣的形式進行了註解,所以,你可以把這種新的機制看成是一種語法糖,它提供了Java程序員之前利用的慣用法類似的功能。為了確保與反射方法在行為上的一致性,註解會被封裝到一個容器中。Java API中的getAnnotation(Class<T> annotation-Class)方法會為註解元素返回類型為 T的註解。如果實際情況有多個類型為T的註解,該方法的返回到底是哪一個呢?

我們不希望一下子就陷入細節的魔咒,類Class提供了一個新的getAnnotationsByType方法,它可以幫助我們更好地使用重複註解。比如,你可以像下面這樣打印輸出Book類的所有Author註解:

public static void main(String args) {
    Author authors = Book.class.getAnnotationsByType(Author.class);    ←─返回一個由重複註解Author組成的數組
    Arrays.asList(authors).forEach(a -> { System.out.println(a.name); });
}

  

這段代碼要正常工作的話,需要確保重複註解及它的容器都有運行時保持策略。關於與遺留反射方法的兼容性的更多討論,可以參考http://cr.openjdk.java.net/~abuckley/8misc.pdf。

A.1.2 類型註解

從Java 8開始,註解已經能應用於任何類型。這其中包括new操作符、類型轉換、instanceof檢查、泛型類型參數,以及implementsthrows子句。這裡,我們舉了一個例子,這個例子中類型為String的變量name不能為空,所以我們使用了@NonNull對其進行註解:

@NonNull String name = person.getName;

  

類似地,你可以對列表中的元素類型進行註解:

List<@NonNull Car> cars = new ArrayList<>;

  

為什麼這麼有趣呢?實際上,利用好對類型的註解非常有利於我們對程序進行分析。這兩個例子中,通過這一工具我們可以確保getName不返回空,cars列表中的元素總是非空值。這會極大地幫助你減少代碼中不期而至的錯誤。

Java 8並未提供官方的註解或者一種工具能以開箱即用的方式使用它們。它僅僅提供了一種功能,你使用它可以對不同的類型添加註解。幸運的是,這個世界上還存在一個名為Checker的框架,它定義了多種類型註解,使用它們你可以增強類型檢查。如果對此感興趣,我們建議你看看它的教程,地址鏈接為:http://www.checker-framework.org。關於在代碼中的何處使用註解的更多內容,可以訪問http://docs.oracle.com/javase/specs/jls/se8/html/jls-9.html#jls-9.7.4。

A.2 通用目標類型推斷

Java 8對泛型參數的推斷進行了增強。相信你對Java 8之前版本中的類型推斷已經比較熟悉了。比如,Java中的方法emptyList方法定義如下:

static <T> List<T> emptyList;

  

emptyList方法使用了類型參數T進行參數化。你可以像下面這樣為該類型參數提供一個顯式的類型進行函數調用:

List<Car> cars = Collections.<Car>emptyList;

  

不過Java也可以推斷泛型參數的類型。上面的代碼和下面這段代碼是等價的:

List<Car> cars = Collections.emptyList;

  

Java 8出現之前,這種推斷機制依賴於程序的上下文(即目標類型),具有一定的局限性。比如,下面這種情況就不大可能完成推斷:

static void cleanCars(List<Car> cars) {
}
cleanCars(Collections.emptyList);

  

你會遭遇下面的錯誤:

cleanCars (java.util.List<Car>)cannot be applied to
     (java.util.List<java.lang.Object>)

  

為了修復這一問題,你只能像我們之前展示的那樣提供一個顯式的類型參數。

Java 8中,目標類型包括向方法傳遞的參數,因此你不再需要提供顯式的泛型參數:

List<Car> cleanCars = dirtyCars.stream
                               .filter(Car::isClean)
                               .collect(Collectors.toList);

  

通過這段代碼,我們能很清晰地瞭解到,正是伴隨Java 8而來的改進讓你只需要一句Collectors.toList就能完成期望的工作,不再需要編寫像Collectors.<Car>toList這麼複雜的代碼了。