你真的開始用JDK8了嗎?(下)
上篇文章中,看到了JDK8中的Optional和Lambda Expressions帶來程式設計上的改變,甚至程式設計思維的改變。接下來我們繼續看JDK8的Stream和Interface default method給我們帶來的改變
Stream
Stream也是JAVA8的一大特點,這裡的Stream和IO的那個Stream不同,它提供了對集合操作的增強,極大的提高了操作集合物件的便利性。下面我們就通過一個示例來看,使用Stream給我們帶來哪些改變。
有一批學生考試資料,包括學生ID,班級ID,學科,分數。來計算如下指標
- 找出所有語文科目,分數大於60分的學生
- 找出語文科目,排名第一的學生
- 計算學生ID為10的學生的總分
- 所有學生按照總分從大到小
在JDK8之前,實現以上功能也非常簡單的,相信對於一個剛剛學習Java的工程師來說,也很容易實現。不妨大家給自己設定一個限制,如何使用最少的程式碼實現以上功能。這裡留作一個問題思考,下面我們使用Stream API來實現這些需求.
篩選:filter用法
找出所有語文科目,分數大於60分的學生
studentScores.stream()
.filter(s -> "語文".equals(s.getSubject()) && s.getScore() >= 60f)
.collect(Collectors.toList())
.forEach(System.out::println);
排序:sorted用法
找出語文科目,排名第一的學生
Optional<StudentScore> studentScore = studentScores.stream()
.filter(s -> "語文".equals(s.getSubject()))
.sorted((s1, s2) -> s1.getScore() > s2.getScore() ? -1 : 1)
.findFirst();
if (studentScore.isPresent()) {
System.out.println(studentScore.get());
}
統計計算:reduce
計算學生ID為10的學生的總分
Double total = studentScores.stream()
.filter(s -> s.getStudentId() == 10)
.mapToDouble(StudentScore::getScore)
.reduce(0, Double::sum);
System.out.println(total);
分組統計:Collectors
所有學生按照總分從大到小
studentScores.stream()
.collect(Collectors.groupingBy(StudentScore::getStudentId
, Collectors.summingDouble(StudentScore::getScore)))
.entrySet()
.stream()
.sorted((o1, o2) -> o1.getValue() < o2.getValue() ? 1 : -1)
.forEach(System.out::println);
以上的示例已經包括了Stream API的大部分的功能。從以上可以看到,在進行計算時,總是需要使用在集合物件中使用stream()方法,先轉成Stream然後在進行後面的操作,為什麼不直接在集合類下直接實現如下操作呢?Stream和集合類有哪些區別?Oracle給出瞭如下說明
- Stream沒有儲存,它不是資料結構,並不儲存資料。它可以像陣列、生成器等資料來源獲取資料,通過一個計算流進行操作
- 在功能性質上,通過流的操作,不會修改資料來源,比如,filter操作,是從集合的流上獲取一個新的流,而不是將過濾掉的元素從集合上刪除
- 延遲計算,許多流式的計算像filter、map等,是通過懶式的實現。一個數據流操作包括三個基本步驟,資料來源->資料轉換->執行獲取結果。每次轉換原有 Stream 物件不改變,返回一個新的 Stream 物件。資料轉換的操作都是lazy的
- 可以支援無限的大小。雖然集合是有限的,但是流是可以支援無限大小的,像limit(n)或者findFirst可以讓無限的流操作在有限的時間內完成
- 流的元素只能在一次建立中被訪問到一次,像Iterator一樣,必須生成一個新的流來訪問新的元素
Interface default method
在JDK8中,使用forEach方法可以直接遍歷陣列,當然們進入檢視forEach檢視原始碼時,我們在Iterable介面可以看到如下程式碼:
default void forEach(Consumer<? super T> action) {
Objects.requireNonNull(action);
for (T t : this) {
action.accept(t);
}
}
這不是方法的實現麼?是的,在接口裡可以寫實現了。在JDK8中為了支援新特性,必須對原有介面進行改造。需要在不破壞現有的架構情況下在接口裡增加新方法。這也是JAVA8引入Default method的原因。但是引入Default method之後,需要思考兩個問題:
和抽象類區別
當介面有了default method 之後,介面看起來和抽象性是一樣的,但是他們兩個在Java8中還是有區別的。
抽象類,有自己的構造方法,並且可以定義自己的資料結構,但是預設方法只能呼叫介面時使用,並不會涉及到特定介面實現類的狀態。具體使用介面還是抽象類還需要根據具體業務場景來界定
介面的多繼承問題
在java中可以支援多繼承,如果兩個介面實現了同樣的預設方法,那麼應該使用哪個呢?
比如:
public interface DemoA {
default void test() {
System.out.println("I'm DemoA.");
}
}
public interface DemoB {
default void test() {
System.out.println("I'm DemoB.");
}
}
如果一個DemoImpl繼承以上兩個介面,程式碼如下:
public class DemoImpl implements DemoA, DemoB {
}
這時,IDE會在DemoImpl下面有一條紅線,提示不能繼承在DemoA和DemoB中的test方法,需要實現該方法
public class DemoImpl implements DemoA, DemoB {
@Override
public void test() {
DemoB.super.test();
DemoA.super.test();;
}
}
實現該方法,和其他方式類似,你可以呼叫父類中的方法,也可以直接自己實現
Other features
除了以上的一些特性,JDK8中還支援了其他的一些特性值得關注
- 引入了新的Date-Time API(JSR 310)來改進時間、日期的處理。
- 引入新的Nashorn JavaScript引擎,使得我們可以在JVM上開發和執行JS應用。
- 引入了Base64編碼的支援
- 新增了支援陣列的並行處理的parallelSort方法等等