《Java8實戰》-讀書筆記第一章(02)
從方法傳遞到Lambda
接著上次的Predicate,繼續來了解一下,如果繼續簡化程式碼。
把方法作為值來傳遞雖然很有用,但是要是有很多類似與isHeavyApple和isGreenApple這種可能只用一兩次的方法定義一堆確實有點煩人。為了解決這個問題,Java8它引入了一套新記法(匿名函式或Lambda),然你可以這樣寫:
List<Apple> isRedApples = filterApples(FilteringApples.apples, apple -> "red".equals(apple.getColor()));
複製程式碼
或者是:
List<Apple> appleList = filterApples(FilteringApples.apples, apple -> apple.getWeight() < 120
&& "red".equals(apple.getColor()));
複製程式碼
甚至,你都可以不需要使用filterApples這個方法了,直接使用Stream中的filter方法就可以解決了:
List<Apple> isGreenApple = apples.stream().filter(apple -> "green".equals(apple.getColor()))
.collect(Collectors.toList());
複製程式碼
酷,看起來很不錯。所以,你甚至都不需要為只用一次的方法寫定義;這樣的程式碼看起來更簡潔、更清晰,因為你用不著去找自己到底傳遞了什麼程式碼。
流
在剛剛篩選蘋果的過程中,就有使用到Stream(流)其中的一個方法,這個Stream和InputStream、OutputStream是兩個完全不同的東西。Stream它是Java8中的一個核心新特,它是一套新的用來處理集合的API,有很多類似與filter這樣的方法而且使用起來非常的簡單和簡潔,可以簡化大部分程式碼並且在並行的情況下利用多核CPU,能很有效的提升對集合處理的效能。
本章只是簡單的介紹了一下流的使用方式,至於流的詳細用法後面的章節會提到的。
現在,有一串字串,需要進行篩選並且轉為大寫以進行排序,在Java8之前是我們是這麼幹的:
List<String> stringList = Arrays.asList("a1", "a2", "b1", "c1", "c2", "c4", "c3");
List<String> cList = new ArrayList<>();
for (String s : stringList) {
// 篩選出以c開頭的字串
if (s.startsWith("c")) {
// 將以c開頭的字串轉為大寫,新增到集合
cList.add(s.toUpperCase());
}
}
// 排序
Collections.sort(cList);
// 遍歷列印
for (String s : cList) {
System.out.println(s);
}
複製程式碼
這樣的程式碼看起來很頭疼,需要寫這麼長一段的程式碼,在Java8中可以使用Stream進行優化:
List<String> stringList = Arrays.asList("a1", "a2", "b1", "c1", "c2", "c4", "c3");
stringList.stream()
// 篩選出以c開頭的字串
.filter(s -> s.startsWith("c"))
// 將剛剛以c開頭的字串轉為大寫
.map(String::toUpperCase)
// 排序
.sorted()
// 迴圈遍歷
.forEach(System.out::println);
複製程式碼
太棒了,只需要短短的一行程式碼就可以完成!但是,使用Stream它也是有缺點的,它的效能不如foreach的效率高為了解決這個問題,Stream支援並。使用並行能極大的利用多核CPU的優勢,例如說:這些程式碼原本只是用單核進行處理,現在有一臺8核的CPU電腦,那麼它的處理速度就會是單核的八倍。
我們來進行比較一下,生成一個0-100的數字並寫入到檔案中,循序流VS並行流誰的效率更高.
循序流:
long startTime = System.currentTimeMillis();
OutputStream out = new FileOutputStream(new File("D:/integer1.txt"));
IntStream.rangeClosed(0, 100)
.forEach(i -> {
try {
Thread.sleep(100L);
out.write(i);
} catch (IOException | InterruptedException e) {
e.printStackTrace();
}
});
long endTime = System.currentTimeMillis();
System.out.println("循序流:" + (endTime - startTime));
複製程式碼
並行流:
long startTime = System.currentTimeMillis();
OutputStream out = new FileOutputStream(new File("D:/integer2.txt"));
IntStream.rangeClosed(0, 100)
.parallel().forEach(i -> {
try {
Thread.sleep(100L);
out.write(i);
} catch (IOException | InterruptedException e) {
e.printStackTrace();
}
});
long endTime = System.currentTimeMillis();
System.out.println("並行流:" + (endTime - startTime));
複製程式碼
執行結果(I5-6200U的筆記本上執行結果):
循序流:10251
並行流:2620
複製程式碼
效率明顯要比循序流快很多嘛!但是,並行流並不是萬能的,如果把sleep去掉後並且數字加到100萬,你會發現執行的時間比循序流還要長。
去掉sleep並且生成的數字是0-100萬,所消耗的時間:
循序流:2775
並行流:3346
複製程式碼
至於為什麼有時候並行流效率比循序流還低,這個以後的文章會解釋。
預設方法
預設方法是Java8中的一個新特性,它的出現使得介面的升級變得平滑了,因為子類不是必須再去顯式的實現介面中的方法了。
例如:在Java8中,你可以直接呼叫List介面中的sort方法、它是用Java8 List介面中如下所示的預設方法實現的:
default void sort(Comparator<? super E> c) {
Object[] a = this.toArray();
Arrays.sort(a, (Comparator) c);
ListIterator<E> i = this.listIterator();
for (Object e : a) {
i.next();
i.set((E) e);
}
}
複製程式碼
這意味著List的任何實體類都不需要顯式的實現sort,而在以前的Java版本中,除非提供了sort的實現,否則這些實體類都無法編譯通過。但是,預設方法也存在著一些問題,一個類可以實現多個介面,那麼好幾個介面多有同樣的預設方法,那麼這是否意味著Java中有了某種形式的多繼承?如果是多繼承,那麼會不會出現像C++中菱形繼承的問題?這些問題以後的文章中都會有解釋和解決方案。
第一章總結:
- 瞭解了Java8中的一些核心新特性,例如:Lambda表示式、Stream、預設方法。
- 瞭解了Lambda表示式和Stream為程式碼帶來的簡潔性。
- 並行流帶來的好處。
- Java8中的預設方法帶來的好處。
程式碼案例: