本附錄中,我們會討論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
檢查、泛型類型參數,以及implements
和throws
子句。這裡,我們舉了一個例子,這個例子中類型為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
這麼複雜的代碼了。