本文首發於cdream的個人博客,點擊獲得更好的閱讀體驗!
歡迎轉載,轉載請注明出處。
前段時間公司書架多了一本《Java8 實戰》,畢竟久聞lambda的大名,於是借來一閱。這一看,簡直是驚為天人啊,lambda,stream,java8里簡直是滿腦子騷操作,看我的一愣一愣的。我甚至是第一次感覺到了什么叫優雅。

本文主要介紹java8中的流處理,看看java8是怎么愉快的玩耍集合的,讓我們來一起感受java8的魅力吧!
我就隨便舉個例子,看看Stream有多優雅。
// 對蘋果按顏色匯總並績數量
Map<String, Long> appleCount = apples.stream()
.collect(groupingBy(Apple::getColor, counting()));
// 過濾掉顏色為黑色的蘋果,並匯總好蘋果的總金額
Double sum = apples.stream()
.filter(i->"black".equals(i.getColor()))
.collect(toList);
一、lambda表達式
雖然本文重點是stream,但是stream中需要傳遞lambda表達式,所以簡單介紹一下lambda表達式。lambda表達式其實就是匿名函數(anonymous function),是指一類無需定義標識符的函數或子程序。
java中匿名函數的表現形式,只留下入參和方法體中的內容
// 普通函數
public void run(String s){
System.out.print(s+"哈哈");
}
// 我不要名字啦!!!
(s)->System.out.print(s+"哈哈")
誒,過去我們都用對象調方法的,你弄這個沒名的東西啥時候用啊?
java中我們通過函數式接口來使用這種匿名函數。
1.java中只包含一個未實現方法的接口。其中可以有與Object中同名的方法和默認方法(java8中接口方法可以有默認實現)。
2.java中函數式接口使用@FunctionalInterface進行注解。Runnable、Comparator都是函數式接口。
3.java.util.function包下為我們提供很多常用的函數式接口,例如Function
用法舉例:
// 實現Runnable中的run方法,替代匿名內部類。
Runnable r = ()->System.out.print("哈哈");
// 作為參數傳遞。
new Thread(()-> System.out.println("haha")).start();
ArrayList<Apple> list = new ArrayList<>();
list.forEach(i-> System.out.println(i.getWeight()));
// 簡化策略模式
public static List<Apple> filterApples(List<Apple> inventory,ApplePredicate p){
List<Apple> apples = new ArrayList<>();
for(Apple apple : inventory){
if(p.test(apple)){
apples.add(apple);
}
}
return apples;
}
public class BigApple implement ApplePredicate{
@Override
public boolean test(Apple a){
if(a.getWeight>10){
return a
}
}
}
// 這是個簡單的策略模式,根據用戶的需要,創建不同的接口ApplePredicate實現類,調用時傳入不同的實現類就可以,但問題是如果需求過多,創建的實現類也會很多,過於臃腫不方便管理。
xx.filterApple(inventory,new BigApple);
// 使用lambda表達式,不在需要創建BigApple類
xx.filterApple(inventory,i->(i.getWeight>10));
使用lambda表達式可以簡化大量的模板代碼,並且可以向方法直接傳遞代碼。
總之
方法出參入參來自函數式接口
//入參s,返回void
(s)->System.out.println(s);
//入參空,返回void
()->System.out.print("haha");
//入參i,返回i+1
i->i+1
//后面寫代碼塊
apple->{if(apple.getWeiht>5) return "BIG";
else return "small";
}
好了,不多啰嗦了,如果感興趣推薦下面的文章或《Java8實戰》的前三章。
二、Stream
流是什么?
Java API的新成員,它允許你使用聲明式方式處理數據集合(類似sql,通過查詢語句表達,而不是臨時編寫一個實現)。
如果有人說lambda表達式不易於理解,那還勉強可以接受(其實過於復雜的lambda缺失不好閱讀,但通常lambda不會做太復雜的實現),但流真的非常的易懂易用。這個語法糖真的是甜死了。
1.流只能使用一次,遍歷結束就代表這個流被消耗掉了
2.流對集合的操作屬於內部迭代,是流幫助我們操作,而不是外部迭代
3.流操作包含:數據源,中間操作鏈,終端操作三個部分。
基礎流操作
List<Double> collect = list.stream()
// 過濾掉黑色的蘋果
.filter(i -> "black".equals(i.getColor()))
// 讓蘋果按照重量個價格排序
.sorted(Comparator.comparing(Apple::getWeight)
.thenComparing(i->i.getPrice()))
// 篩選掉重復的數據
.distinct()
// 只要蘋果的價格
.map(Apple::getPrice)
// 只留下前兩條數據
.limit(2)
// 以集合的形式返回
.collect(toList());
// 循環打印列表中元素
list.forEach(i->System.out.print(i));
Apple::getPrince<=>i -> i.getPrince()可以看做是僅涉及單一方法的語法糖,效果與lambda表達式相同,但可讀性更好。
同理
下面列表為常見操作
中間
| 操作 | 類型 | 作用 | 函數描述 | 函數 |
|---|---|---|---|---|
| filter | 中間 | 過濾 | T -> boolean | Predicate
|
| sorted | 中間 | 排序 | (T,T)->int | Comparator
|
| map | 中間 | 映射 | T->R | Function<T,R> |
| limit | 中間 | 截斷 | ||
| distinct | 中間 | 去重,根據equals方法 | ||
| skip | 中間 | 跳過前n個元素 |
終端
| 操作 | 類型 | 作用 |
|---|---|---|
| forEach | 終端 | 消費流中的每個元素,使用lambda進行操作 |
| count | 終端 | 返回元素個數,long |
| collect | 終端 | 將流歸約成一個集合,如List,Map甚至是Integer |
篩選與切片
List<String> strings = Arrays.asList("Hello", "World");
List<String> collect1 = strings.stream()
// String映射成String[]
.map(i -> i.split(""))
// Arrays::Stream 數據數組,返回一個流String[]->Stream<String>
// flatMap各數組並不分別映射成一個流,而是映射成流的內容 Stream<String>->Stream
.flatMap(Arrays::stream)
.collect(toList());
System.out.println(collect);
----->輸出 [H, e, l, l, o, W, o, r, l, d]
歸約操作reduce
List<Integer> integers = Arrays.asList(12, 3, 45, 3, 2,-1);
// 有初始值的疊加操作
Integer reduce = integers.stream().reduce(3, (i, j) -> i + j);
Integer reduce2 = integers.stream().reduce(5, (x, y) -> x < y ? x : y);
// 無初始值的疊加操作
Optional<Integer> reduce1 = integers.stream().reduce((i, j) -> i + j);
// 無初始值的最大值
Optional<Integer> reduce4 = integers.stream().reduce(Integer::min);
// 無初始值的最大值
Optional<Integer> reduce5 = integers.stream().reduce(Integer::max);
// 求和
Optional<Integer> reduce6 = integers.stream().reduce(Integer::sum);
reduce做的事情是取兩個數進行操作,結果返回取下一個數操作,以次類推。
Optional是java8引入的新類,避免造成空指針異常,在集合為空時,結果會包在Optional中,可以用isPresent()方法來判斷是否為空值。
無初始值的情況下可能為空,故返回Optional
中間
| 操作 | 類型 | 作用 | 函數描述 | 函數 |
|---|---|---|---|---|
| flatmap | 中間 | 使通過的流返回內容 | T -> boolean | Predicate
|
終端
| 操作 | 類型 | 作用 |
|---|---|---|
| anyMatch | 終端 | 返回boolean,判斷是否有符合條件內容 |
| noneMatch | 終端 | 返回boolean,判斷是否無符合條件內容 |
| allMatch | 終端 | 返回boolean,判斷是全為符合條件內容 |
| findAny | 終端 | Optional
|
| findFirst | 終端 | Optional
|
| reduce | 終端 | Optional
|
數值流
包裝類型的各種操作都會有拆箱操作和裝箱操作,嚴重影響性能。所以Java8為我們提供了原始數值流。
// 數值流求平均值
OptionalDouble average = apples.stream()
.mapToDouble(Apple::getPrice)
.average();
// 數值流求和
OptionalDouble average = apples.stream()
.mapToDouble(Apple::getPrice)
.sum();
// 數值流求最大值,沒有則返回2
double v = apples.stream()
.mapToDouble(Apple::getPrice)
.max().orElse(2);
// 生成隨機數
IntStream s = IntStream.rangeClosed(1,100);
下面列表為常見數值流操作操作
中間
| 操作 | 類型 | 作用 |
|---|---|---|
| rangeClosed(1,100) | 中間 | 生成隨機數(1,100] |
| range(1,100) | 中間 | 生成隨機數(1,100) |
| boxed() | 中間 | 包裝成一般流 |
| mapToObj | 中間 | 返回為對象流 |
| mapToInt | 中間 | 映射為數值流 |
終端,終端操作與List
構建流
-
值創建
Stream<String> s = Stream.of("java","python"); -
數組創建
int[] i = {2,3,4,5}; Stream<int> = Arrays.stream(i); -
由文件生成,NIO API已經更新,以便利用Stream API
Stream<String> s = Files.lines(Paths.get("data.txt"),Charset.defaultCharset()); -
由函數創建流:無限流
// 迭代 Stream.iterate(0,n->n+2) .limit(10) .forEach(System.out::println); // 生成,需要傳遞實現Supplier<T>類型的Lambda提供的新值 Stream.generate(Math.random) .limit(5) .forEach(System.out::println);
三、總結
至此,本文講述了常見的流操作,目前排序、篩選、求和、歸約等大多數操作我們都能實現了。與過去相比,操作集合變的簡單多了,代碼也變的更加簡練明了。
目前Vert.x,Spring新出的WebFlux都通過lambda表達式來簡化代碼,不久的將來,非阻塞式框架的大行其道時,lambda表達式必將變的更加重要!
至於開篇見到的分組!!!下篇文章見~
參考資料:
