Lambda 表達式(Method References)
lambda 表達式使用一種簡潔的方式來傳遞代碼片段。舉個例子,假如你需要使用一個線程(Thread)執行一個任務。你可能這樣做:創建一個Runnable對象,然後將該對象作爲參數傳遞給Thread。
Runnable runnable = new Runnable() {
@Override
public void run() {
System.out.println("Hi");
}
};
new Thread(runnable).start();
另一方面,你可以使用lambda 表達式以這一種更加易讀的方式重寫之前的代碼。
new Thread(() -> System.out.println("Hi")).start();
方法引用(Method References)
方法引用與lambda 表達式配合使用形成一種新特性。讓你選擇一個在類中已經存在的方法作爲參數傳遞。舉個例子,假如你需要忽略大小寫來比較一個字符串列表。常規地我們像這樣寫代碼:
List<String> strs = Arrays.asList("C", "a", "A", "b");
Collections.sort(strs, new Comparator<String>() {
@Override
public int compare(String s1, String s2) {
return s1.compareToIgnoreCase(s2);
}
});
上面的代碼及其冗長囉嗦。畢竟你需要的只是方法compareToIgnoreCase。你可以使用方法引用很明確地表述出,比較大小應該使用定義在String類中的方法compareToIgnoreCase來執行:
Collections.sort(strs, String::compareToIgnoreCase);
代碼String::compareToIgnoreCase就叫做方法引用。它使用特別的語法 ::。
流(Streams)
幾乎每一個Java程序都會創建和處理集合。我們經常使用集合來歸類和處理數據,因此集合對許多編程任務非常重要。然而,使用集合寫出的代碼相當地冗長和難以並行化。下面的代碼闡述了處理集合是多麼的麻煩。這段代碼主要功能:查找按照清單費用大小排序的培訓相關費用清單ID。
List<Invoice> trainingInvoices = new ArrayList<>();
for(Invoice inv: invoices) {
if(inv.getTitle().contains("Training")) {
trainingInvoices.add(inv);
}
}
Collections.sort(trainingInvoices, new Comparator() {
public int compare(Invoice inv1, Invoice inv2) {
return inv2.getAmount().compareTo(inv1.getAmount());
}
});
List<Integer> invoiceIds = new ArrayList<>();
for(Invoice inv: trainingInvoices) {
invoiceIds.add(inv.getId());
Java 8 引進了一種新的抽象方式叫做流,它可以讓我們聲明式地處理數據。在Java 8 中,我們可以使用流(stream)重新上文代碼:
List<Integer> invoiceIds =
invoices.stream()
.filter(inv -> inv.getTitle().contains("Training"))
.sorted(comparingDouble(Invoice::getAmount)
.reversed())
.map(Invoice::getId)
.collect(Collectors.toList());
另外還可以使用集合類中的方法parallelStream來代替方法stream來顯式地並行執行數據流。
增強接口(Enhanced Interfaces)
Java 8 中接口增強得益於兩種改進方式從而可以在聲明方法時附帶具體實現。
第一,Java 8 引進默認方法(default methods),它可以讓我們在接口中聲明方法時附帶具體實現代碼。這是Java 8 引入用於解決Java API 向後兼容的機制。
比如,我們可以看到Java 8 中的List接口現在已經支持sort方法:
default void sort(Comparator<? super E> c) {
Collections.sort(this, c);
}
從行爲方面看,默認方法也可以服務於多重繼承服務機制。實際上,在Java 8 之前的版本中,一個類已經可以實現多個接口。現在,你可以繼承多個不同的接口的默認方法。注意,Java 8 擁有顯式的規則來避免一些在C++中常見的繼承問題(例如鑽石問題)。
第二,接口現在可以有靜態方法(static methods)。在一個常規模式下,我們定義一個接口,與此同時定義一個伴隨類,伴隨類定義一些靜態方法與接口的繼承類協同工作。例如,Java中有Collection接口和Collections類,Collections類用來定義工具靜態方法。現在這些工具靜態方法可以直接定義在接口中。
看具體事例,在Java 8 中Stream接口聲明一個靜態方法:
public static <T> Stream<T> of(T... values) {
return Arrays.stream(values);
}
新的日期和時間 API(New Date and Time API)
Java 8 中引進了一種新的日期和時間 API 來解決許多舊的類Date和Calendar的典型問題。新的日期和時間圍繞兩大原則設計:
領域驅動設計(Domain-driven)
新的日期和時間 API 通過產生新的類來代替本身很好地模型化各種意圖的日期和時間。例如,你可以使用類Period來表示一個"2個月零3天"的值,使用類ZonedDateTime來表示帶有時區的日期時間。每個類都提供了採用流暢方表達的領域特定方法。因此,你可以鏈接方法來寫出更加易讀的代碼。
例如,下面的代碼描述瞭如何創建一個LocalDateTime對象同時在這個時間基礎上增加2小時30分鐘。
LocatedDateTime coffeeBreak = LocalDateTime.now()
.plusHours(2)
.plusMinutes(30);
不可變(Immutability)
類Date和Calendar有一個問題就是不是線程安全的。另外,開發人員在使用日期作爲API的一部分時偶爾會無預期地改變日期值。爲了避免潛在的Bug,在新的日期和時間 API 中的類都是不可變的。換句話說,你將不能改變在新的日期和時間 API 中的對象狀態。現在你可以使用一個方法來返回帶有更新後值的新對象。
下面的代碼列舉各種在新的日期和時間 API 中的方法:
ZoneId london = ZoneId.of("Europe/London");
LocalDate july4 = LocalDate.of(2014, Month.JULY, 4);
LocalTime early = LocalTime.parse("08:45");
ZonedDateTime flightDeparture = ZonedDateTime.of(july4, early, london);
System.out.println(flightDeparture);
LocalTime from = LocalTime.from(flightDeparture);
System.out.println(from);
ZonedDateTime touchDown
= ZonedDateTime.of(july4,
LocalTime.of (11, 35),
ZoneId.of("Europe/Stockholm"));
Duration flightLength = Duration.between(flightDeparture, touchDown);
System.out.println(flightLength);
// How long have I been in continental Europe?
ZonedDateTime now = ZonedDateTime.now();
Duration timeHere = Duration.between(touchDown, now);
System.out.println(timeHere);
這段代碼會產生類似這樣的輸出:
2015-07-04T08:45+01:00[Europe/London]
08:45
PT1H50M
PT269H46M55.736S
CompletableFuture
Java 8 使用一個新類CompletableFuture來處理異步程序。它是針對舊的類Future的提升,它的方法啓發於新的流 API 類似的設計選擇(例如,聲明式風格和流暢地方法鏈接)。換句話說,你可以聲明式地處理和組合多個異步任務。有這樣一個事例,併發地查詢兩個支持匯率計算的價格查詢服務的任務。當兩個服務都可用並且返回結果時,你可以組合結果以英鎊(GBP)形式來計算和打印出來。
findBestPrice("iPhone6")
.thenCombine(lookupExchangeRate(Currency.GBP),this::exchange)
.thenAccept(localAmount -> System.out.printf("It will cost
you %f GBP\n", localAmount));
private CompletableFuture<Price> findBestPrice(String productName) {
return CompletableFuture.supplyAsync(() -> priceFinder.findBestPrice(productName));
}
private CompletableFuture<Double> lookupExchangeRate(Currency localCurrency) {
return CompletableFuture.supplyAsync(() ->
exchangeService.lookupExchangeRate(Currency.USD, localCurrency));
}
Optional
Java 8 引入一個新的類叫做Optional,啓發於函數式編程語言(functional programming languages)。當一個值可能存在或者不存在時我們可以使用它更好地構建自己的代碼庫。可以認爲它是一個單一值的容器,這個容器可以包含一個有效值或者一個空值。Optional常見於特殊的集合框架中(例如Guava),但是現在它也成爲了Java API 的一部分。Optional還有一個好處就是可以有效地避免空指針異常(NullPointerException)。實際上,通過Optional定義一個方法強制你檢查一個值是否存在。看下面的代碼:
getEventWithId(10).getLocation().getCity();
如果getEventWithId返回空(null),那麼這段代碼就會拋出空指針異常。如果getLocation返回空它還是會拋出空指針異常。也就是說任意一個方法返回空都會拋出空指針異常。你可以採用防禦檢查來避免這種情況。看下面的代碼:
public String getCityForEvent(int id) {
Event event = getEventWithId(id);
if(event != null) {
Location location = event.getLocation();
if(location != null) {
return location.getCity();
}
}
return "TBC";
}
這段代碼中,event可能有一個location,location可能有一個city。非常煩人的時我們經常忘記去檢查空值。
另外,這段代碼相當囉嗦且相比較於下面的代碼更加複雜。
public String getCityForEvent(int id) {
Optional.ofNullable(getEventWithId(id))
.flatMap(this::getLocation)
.map(this::getCity)
.orElse("TBC");
}
可以看到使用Optional重構的代碼更加簡潔明瞭。
在任何時候,這要Optional返回空時都會使用“TBC”來作爲默認值返回。