一、Stream流
1. 流的基本概念
1.1 什么是流?
流是Java8引入的全新概念,它用來處理集合中的數據,暫且可以把它理解為一種高級集合。
眾所周知,集合操作非常麻煩,若要對集合進行篩選、投影,需要寫大量的代碼,而流是以聲明的形式操作集合,它就像SQL語句,我們只需告訴流需要對集合進行什么操作,它就會自動進行操作,並將執行
結果交給你,無需我們自己手寫代碼。
因此,流的集合操作對我們來說是透明的,我們只需向流下達命令,它就會自動把我們想要的結果給我們。由於操作過程完全由Java處理,因此它可以根據當前硬件環境選擇最優的方法處理,我們也無需編
寫復雜又容易出錯的多線程代碼了。
1.2 流的特點
只能遍歷一次
我們可以把流想象成一條流水線,流水線的源頭是我們的數據源(一個集合),數據源中的元素依次被輸送到流水線上,我們可以在流水線上對元素進行各種操作。一旦元素走到了流水線的另一頭,那么這些
元素就被“消費掉了”,我們無法再對這個流進行操作。當然,我們可以從數據源那里再獲得一個新的流重新遍歷一遍。
采用內部迭代方式
若要對集合進行處理,則需我們手寫處理代碼,這就叫做外部迭代。而要對流進行處理,我們只需告訴流我們需要什么結果,處理過程由流自行完成,這就稱為內部迭代。
1.3 流的操作種類
流的操作分為兩種,分別為中間操作 和 終端操作。
中間操作
當數據源中的數據上了流水線后,這個過程對數據進行的所有操作都稱為“中間操作”。
中間操作仍然會返回一個流對象,因此多個中間操作可以串連起來形成一個流水線。
終端操作
當所有的中間操作完成后,若要將數據從流水線上拿下來,則需要執行終端操作。
終端操作將返回一個執行結果,這就是你想要的數據。
1.4 流的操作過程
使用流一共需要三步:
准備一個數據源
執行中間操作
中間操作可以有多個,它們可以串連起來形成流水線。
執行終端操作
執行終端操作后本次流結束,你將獲得一個執行結果。
2. 流的使用
2.1 獲取流
在使用流之前,首先需要擁有一個數據源,並通過StreamAPI提供的一些方法獲取該數據源的流對象。數據源可以有多種形式:
集合
這種數據源較為常用,通過stream()方法即可獲取流對象:
List<Person> list = new ArrayList<Person>();
Stream<Person> stream = list.stream();
數組
通過Arrays類提供的靜態函數stream()獲取數組的流對象:
String[] names = {"chaimm","peter","john"};
Stream<String> stream = Arrays.stream(names);
值
直接將幾個值變成流對象:
Stream<String> stream = Stream.of("chaimm","peter","john");
文件
try(Stream lines = Files.lines(Paths.get(“文件路徑名”),Charset.defaultCharset())){
//可對lines做一些操作
}catch(IOException e){
}
PS:Java7簡化了IO操作,把打開IO操作放在try后的括號中即可省略關閉IO的代碼。
2.2 篩選filter
filter函數接收一個Lambda表達式作為參數,該表達式返回boolean,在執行過程中,流將元素逐一輸送給filter,並篩選出執行結果為true的元素。
如,篩選出所有學生:
List<Person> result =
Predicate<String> startWith = (n) -> n.startsWith("b");
Predicate<String> lengthWith = (n) -> n.length() ==3;
list.stream().filter(startWith.and(lengthWith)).forEach((n) -> System.out.println("stream:"+n));
2.3 去重distinct
去掉重復的結果:
List<Person> result = list.stream().distinct().collect(toList());
2.4 截取
截取流的前N個元素:
List<Person> result = list.stream().limit(3).collect(toList());
2.5 跳過
跳過流的前n個元素:
List<Person> result = list.stream()
.skip(3)
.collect(toList());
2.6 映射
對流中的每個元素執行一個函數,使得元素轉換成另一種類型輸出。流會將每一個元素輸送給map函數,並執行map中的Lambda表達式,最后將執行結果存入一個新的流中。
如,獲取每個人的姓名(實則是將Perosn類型轉換成String類型):
List<Person> result = list.stream().map(Person::getName).collect(toList());
2.7 合並多個流
例:列出List中各不相同的單詞,List集合如下:
List<String> list = new ArrayList<String>();
list.add("I am a boy");
list.add("I love the girl");
list.add("But the girl loves another girl");
思路如下:
首先將list變成流:
list.stream();
按空格分詞:
list.stream().map(line->line.split(" "));
分完詞之后,每個元素變成了一個String[]數組。
將每個String[]變成流:
list.stream().map(line->line.split(" ")).map(Arrays::stream)
此時一個大流里面包含了一個個小流,我們需要將這些小流合並成一個流。
將小流合並成一個大流:
用flagmap替換剛才的map
list.stream().map(line->line.split(" ")).flagmap(Arrays::stream)
去重
list.stream().map(line->line.split(" ")).flagmap(Arrays::stream).distinct().collect(toList());
2.8 是否匹配任一元素:anyMatch
anyMatch用於判斷流中是否存在至少一個元素滿足指定的條件,這個判斷條件通過Lambda表達式傳遞給anyMatch,執行結果為boolean類型。
如,判斷list中是否有學生:
boolean result = list.stream().anyMatch(Person::isStudent);
2.9 是否匹配所有元素:allMatch
allMatch用於判斷流中的所有元素是否都滿足指定條件,這個判斷條件通過Lambda表達式傳遞給anyMatch,執行結果為boolean類型。
如,判斷是否所有人都是學生:
boolean result = list.stream().allMatch(Person::isStudent);
2.10 是否未匹配所有元素:noneMatch
noneMatch與allMatch恰恰相反,它用於判斷流中的所有元素是否都不滿足指定條件:
boolean result = list.stream().noneMatch(Person::isStudent);
2.11 獲取任一元素findAny
findAny能夠從流中隨便選一個元素出來,它返回一個Optional類型的元素。
Optional<Person> person = list.stream()
.findAny();
Optional介紹
Optional是Java8新加入的一個容器,這個容器只存1個或0個元素,它用於防止出現NullpointException,它提供如下方法:
isPresent()
判斷容器中是否有值。
ifPresent(Consume lambda)
容器若不為空則執行括號中的Lambda表達式。
T get()
獲取容器中的元素,若容器為空則拋出NoSuchElement異常。
T orElse(T other)
獲取容器中的元素,若容器為空則返回括號中的默認值。
2.12 獲取第一個元素findFirst
Optional<Person> person = list.stream().findFirst();
2.13 歸約
歸約是將集合中的所有元素經過指定運算,折疊成一個元素輸出,如:求最值、平均數等,這些操作都是將一個集合的元素折疊成一個元素輸出。
在流中,reduce函數能實現歸約。
reduce函數接收兩個參數:
初始值
進行歸約操作的Lambda表達式
2.13.1 元素求和:自定義Lambda表達式實現求和
例:計算所有人的年齡總和
int age = list.stream().reduce(0, (person1,person2)->person1.getAge()+person2.getAge());
reduce的第一個參數表示初試值為0;
reduce的第二個參數為需要進行的歸約操作,它接收一個擁有兩個參數的Lambda表達式,reduce會把流中的元素兩兩輸給Lambda表達式,最后將計算出累加之和。
2.13.2 元素求和:使用Integer.sum函數求和
上面的方法中我們自己定義了Lambda表達式實現求和運算,如果當前流的元素為數值類型,那么可以使用Integer提供了sum函數代替自定義的Lambda表達式,如:
int age = list.stream().reduce(0, Integer::sum);
Integer類還提供了min、max等一系列數值操作,當流中元素為數值類型時可以直接使用。
2.14 數值流的使用
采用reduce進行數值操作會涉及到基本數值類型和引用數值類型之間的裝箱、拆箱操作,因此效率較低。
當流操作為純數值操作時,使用數值流能獲得較高的效率。
2.14.1 將普通流轉換成數值流
StreamAPI提供了三種數值流:IntStream、DoubleStream、LongStream,也提供了將普通流轉換成數值流的三種方法:mapToInt、mapToDouble、mapToLong。
如,將Person中的age轉換成數值流:
IntStream stream = list.stream().mapToInt(Person::getAge);
2.14.2 數值計算
每種數值流都提供了數值計算函數,如max、min、sum等。
如,找出最大的年齡:
OptionalInt maxAge = list.stream().mapToInt(Person::getAge).max();
3. 由於數值流可能為空,並且給空的數值流計算最大值是沒有意義的,因此max函數返回OptionalInt,它是Optional的一個子類,能夠判斷流是否為空,並對流為空的情況作相應的處理。
此外,mapToInt、mapToDouble、mapToLong進行數值操作后的返回結果分別為:OptionalInt、OptionalDouble、OptionalLong
4. Collect
4.1 collect是一個終端操作,它接收的參數是將流中的元素累積到匯總結果的各種方式(稱為收集器)
4.2 預定義收集器包括將流元素歸約和匯總到一個值.如下
工廠方法 |
返回類型 |
用於 |
toList |
List<T> |
把流中所有元素收集到List中 |
示例:List<Menu> menus=Menu.getMenus.stream().collect(Collector.toList()) |
||
toSet |
Set<T> |
把流中所有元素收集到Set中,刪除重復項 |
示例:Set<Menu> menus=Menu.getMenus.stream().collect(Collector.toSet()) |
||
toCollection |
Collection<T> |
把流中所有元素收集到給定的供應源創建的集合中 |
示例:ArrayList<Menu> menus=Menu.getMenus.stream().collect(Collector.toCollection(ArrayList::new)) |
||
Counting |
Long |
計算流中元素個數 |
示例:Long count=Menu.getMenus.stream().collect(counting); |
||
SummingInt |
Integer |
對流中元素的一個整數屬性求和 |
示例:Integer count=Menu.getMenus.stream().collect(summingInt(Menu::getCalories)) |
||
averagingInt |
Double |
計算流中元素integer屬性的平均值 |
示例:Double averaging=Menu.getMenus.stream().collect(averagingInt(Menu::getCalories)) |
||
Joining |
String |
連接流中每個元素的toString方法生成的字符串 |
示例:String name=Menu.getMenus.stream().map(Menu::getName).collect(joining(“, ”)) |
||
maxBy |
Optional<T> |
一個包裹了流中按照給定比較器選出的最大元素的optional 如果為空返回的是Optional.empty() |
示例:Optional<Menu> fattest=Menu.getMenus.stream().collect(maxBy(Menu::getCalories)) |
||
minBy |
Optional<T> |
一個包裹了流中按照給定比較器選出的最大元素的optional 如果為空返回的是Optional.empty() |
示例: Optional<Menu> lessest=Menu.getMenus.stream().collect(minBy(Menu::getCalories)) |
||
Reducing |
歸約操作產生的類型 |
從一個作為累加器的初始值開始,利用binaryOperator與流中的元素逐個結合,從而將流歸約為單個值 |
示例:int count=Menu.getMenus.stream().collect(reducing(0,Menu::getCalories,Integer::sum)); |
||
collectingAndThen |
轉換函數返回的類型 |
包裹另一個轉換器,對其結果應用轉換函數 |
示例:Int count=Menu.getMenus.stream().collect(collectingAndThen(toList(),List::size)) |
||
groupingBy |
Map<K,List<T>> |
根據流中元素的某個值對流中的元素進行分組,並將屬性值做為結果map的鍵 |
示例:Map<Type,List<Menu>> menuType=Menu.getMenus.stream().collect(groupingby(Menu::getType)) |
||
partitioningBy |
Map<Boolean,List<T>> |
根據流中每個元素應用謂語的結果來對項目進行分區 |
示例:Map<Boolean,List<Menu>> menuType=Menu.getMenus.stream().collect(partitioningBy(Menu::isType)); |
4.3 預定義收集器可以用groupby對流中元素進行分組或者用partitioningBy進行分區
4.4 收集器可以高效的復合起來,進行多級分組,多級分區和歸約
4.5 可以自己實現collector接口進行定義自己的收集器
二、總結
1. Stream API 常用操作如下:
操作 |
類型 |
返回類型 |
函數式接口 |
函數描述符 |
filter |
中間 |
Stream<T> |
Predicate<T> |
T->Boolean |
distinct |
中間-有狀態 |
Stream<T> |
|
|
Skip |
中間-有狀態 |
Stream<T> |
Long |
|
Limit |
中間-有狀態 |
Stream<T> |
Long |
|
Map |
中間 |
Stream<T> |
Function<T,R> |
T->R |
Flatmap |
中間 |
Stream<T> |
Function<T,Stream<R>> |
T->Stream<R> |
Sorted |
中間-有狀態 |
Stream<T> |
Compartor<T> |
(T,T)->int |
anyMatch |
終端 |
Boolean |
Predicate<T> |
T->Boolean |
noneMatch |
終端 |
Boolean |
Predicate<T> |
T->Boolean |
allMatch |
終端 |
Boolean |
Predicate<T> |
T->Boolean |
findAny |
終端 |
Optional<T> |
|
|
findFirst |
終端 |
Optional<T> |
|
|
forEach |
終端 |
Void |
Consumer<T> |
T->void |
Collect |
終端 |
R |
Collector<T,A,R> |
|
Reduce |
終端-有狀態 |
Optional<T> |
BinaryOperator<T> |
(T,T)->T |
Count |
終端 |
Long |
|
|
2. 可以使用filter,distinct,skip和limit對流進行篩選和切片
3. 可以使用map和flatMap提取或轉換流中的元素
4. 可以使用findFirst和findAny方法查找流中的元素.你可以用allMatch,noneMatch和anyMatch方法讓流匹配給定的謂語
5. 上述方法都利用了短路:找到結果就立刻停止計算,並沒有必要處理整個流
6. 可以利用reduce方法將流中的所有元素迭代合並成一個結果,例如求和或者查詢最大的元素
7. filter和map等操作都是無狀態的,他們並沒有儲存任何狀態.reduce等操作要儲存狀態才能計算出一個值.sorted和distinct等操作也要儲存狀態,因為他們需要把流中的所有元素緩存起來才能返回一個新的流.這種操作稱為有狀態操作.
8. 流有三種基本原始類型特化:intStream,doubleStream和LongStream.他們的操作也有相應的特化
9. 流不僅可以從集合創建,也可以從值,數組,文件以及iterate與generate等特定方法創建
10.Demo案例
public static Map<String, Integer> sumIntByGroup(List<Map> in, String sumKey, String groupKey) { return in.stream() .collect( groupingBy(item -> (String) item.get(groupKey),//根據groupKey字段分組 TreeMap::new, Collectors.summingInt(item -> Integer.parseInt(String.valueOf(item.get(sumKey))))//對sumKey做匯總 ) ); } //分組求和 public static Map<String, Double> sumDoubleByGroupWithGroupFunction(List<Map> in, String sumKey, String groupKey, Function<String, String> groupKeyFunction) { return in.stream() .collect( groupingBy(item -> groupKeyFunction.apply((String) item.get(groupKey)), TreeMap::new, Collectors.summingDouble(item -> Double.valueOf(String.valueOf(item.get(sumKey))))//對時長做匯總 ) ); } //求和 public static Double sumDouble(List<Map> in, String sumKey) { return (Double) in.stream().mapToDouble(item -> Double.valueOf(String.valueOf(item.get(sumKey)))).sum(); } //求和 public static Integer sumInt(List<Map> in, String sumKey) { return (Integer) in.stream().mapToInt(item -> Integer.valueOf(String.valueOf(item.get(sumKey)))).sum(); } //求和 public static Double sumDoubleWithFunction(List<Map> in, String sumKey, Function<String, String> groupKeyFunction) { return (Double) in.stream().mapToDouble(item -> Double.valueOf(String.valueOf(item.get(groupKeyFunction.apply(sumKey))))).sum(); } //分組求列表 public static Map<String, List<String>> obtainMapByGroupWithGroupFunction (List<Map> in, String listKey, String groupKey, Function<String, String> groupKeyFunction) { return in.stream() .collect( groupingBy(item -> groupKeyFunction.apply(String.valueOf(item.get(groupKey))), Collectors.mapping(item -> String.valueOf(item.get(listKey)), Collectors.toList()) ) ); } //分組求列表 public static Map<String, List<String>> obtainMapByGroupWithGroupFunctionAndWhere (List<Map> in, String listKey, String groupKey, Function<String, String> groupKeyFunction, String whereKey, String whereOper, String whereValue) { switch (whereOper) { case "<=": return in.stream().filter(o -> String.valueOf(o.get(whereKey)).compareTo(whereValue) <= 0) .collect( groupingBy(item -> groupKeyFunction.apply(String.valueOf(item.get(groupKey))), Collectors.mapping(item -> String.valueOf(item.get(listKey)), Collectors.toList()) ) ); case ">=": return in.stream().filter(o -> String.valueOf(o.get(whereKey)).compareTo(whereValue) >= 0) .collect( groupingBy(item -> groupKeyFunction.apply(String.valueOf(item.get(groupKey))), Collectors.mapping(item -> String.valueOf(item.get(listKey)), Collectors.toList()) ) ); case "==": default: return in.stream().filter(o -> String.valueOf(o.get(whereKey)).compareTo(whereValue) == 0) .collect( groupingBy(item -> groupKeyFunction.apply(String.valueOf(item.get(groupKey))), Collectors.mapping(item -> String.valueOf(item.get(listKey)), Collectors.toList()) ) ); } } //分組求列表 public static Map<String, List<String>> obtainListByGroupWithSetFunction(List<Map> in, String setKey, String groupKey, Function<String, String> setKeyFunction) { return in.stream() .collect( groupingBy(item -> String.valueOf(item.get(groupKey)), Collectors.mapping(item -> setKeyFunction.apply(String.valueOf(item.get(setKey))), Collectors.toList()) ) ); } //分組求集合 public static Map<String, Set<String>> obtainSetByGroupWithGroupFunction(List<Map> in, String setKey, String groupKey, Function<String, String> groupKeyFunction) { return in.stream() .collect( groupingBy(item -> groupKeyFunction.apply(String.valueOf(item.get(groupKey))), Collectors.mapping(item -> String.valueOf(item.get(setKey)), Collectors.toSet()) ) ); } //求唯一值列表 public static Set<String> obtainSet(List<Map> in, String distinctKey) { return in.stream() .map(item -> String.valueOf(item.get(distinctKey))) .collect(Collectors.toSet()); } //求指定key的list集合 public static List<String> obtainForList(List<JSONObject> in, String listKey) { return in.stream() .map(item -> String.valueOf(item.get(listKey))) .collect(Collectors.toList()); } //求指定key的list集合並且去重 public static List<String> obtainForListQc(List<JSONObject> in, String listKey) { return in.stream() .map(item -> String.valueOf(item.get(listKey))) .collect(Collectors.toList()).stream().distinct().collect(Collectors.toList()); }