Stream和集合的區別:
Stream不會自己存儲元素。元素儲存在底層集合或者根據需要產生。
Stream操作符不會改變源對象。相反,它會返回一個持有結果的新的Stream。
3.Stream操作可能是延遲執行的,這意味着它們會等到需要結果的時候才執行。
Stream操作的基本過程,可以歸結為3個部分:
創建一個Stream。
在一個或者多個操作中,將指定的Stream轉換為另一個Stream的中間操作。
通過終止(terminal)方法來產生一個結果。該操作會強制它之前的延時操作立即執行,這之后該Stream就不能再被使用了。
中間操作都是filter()、distinct()、sorted()、map()、flatMap()等,其一般是對數據集的整理(過濾、排序、匹配、抽取等)。
終止方法往往是完成對數據集中數據的處理,如forEach(),還有allMatch()、anyMatch()、findAny()、 findFirst(),數值計算類的方法有sum、max、min、average等等。終止方法也可以是對集合的處理,如reduce()、 collect()等等。reduce()方法的處理方式一般是每次都產生新的數據集,而collect()方法是在原數據集的基礎上進行更新,過程中不產生新的數據集。
List<integer>nums = Arrays.asList(1, 3, null, 8, 7, 8, 13, 10);
nums.stream().filter(num -> num != null).distinct().forEach(System.out::println);
上面代碼實現為過濾null值並去重,遍歷結果,實現簡潔明了。使用傳統方法就相對繁瑣的多。另外其中 forEach即為終止操作方法,如果無該方法上面代碼就沒有任何操作。filter、map、forEach、findAny等方法的使用都比較簡單,這里省略。</integer>
下面介紹強大的聚合操作,其主要分為兩種:
可變聚合:把輸入的元素們累積到一個可變的容器中,比如Collection或者StringBuilder;
其他聚合:除去可變聚合,剩下的,一般都不是通過反復修改某個可變對象,而是通過把前一次的匯聚結果當成下一次的入參,反復如此。比如reduce,count,allMatch;
聚合操作reduce
Stream.reduce,返回單個的結果值,並且reduce操作每處理一個元素總是創建一個新值。常用的方法有average, sum, min, max, count,使用reduce方法都可實現。這里主要介紹reduce方法:
T reduce(T identity, BinaryOperator<t>accumulator)
identity:它允許用戶提供一個循環計算的初始值。accumulator:計算的累加器,其方法簽名為apply(T t,U u),在該reduce方法中第一個參數t為上次函數計算的返回值,第二個參數u為Stream中的元素,這個函數把這兩個值計算apply,得到的和會被賦值給下次執行這個方法的第一個參數。有點繞看代碼:</t>
int value = Stream.of(1, 2, 3, 4).reduce(100, (sum, item) -> sum item);
Assert.assertSame(value, 110);
/* 或者使用方法引用 */
value = Stream.of(1, 2, 3, 4).reduce(100, Integer::sum);
這個例子中100即為計算初始值,每次相加計算值都會傳遞到下一次計算的第一個參數。
reduce還有其它兩個重載方法:
Optional<t>reduce(BinaryOperator<t>accumulator):與上面定義基本一樣,無計算初始值,所以他返回的是一個Optional。
<span style="TEXT-DECORATION: underline">U reduce(U identity, BiFunction<u,? u="" super=""> accumulator, BinaryOperator<span style="TEXT-DECORATION: underline"> combiner):與前面兩個參數的reduce方法幾乎一致,你只要注意到BinaryOperator其實實現了BiFunction和BinaryOperator兩個接口。
收集結果collect</span></u,?></span></t></t>
當你處理完流時,通常只是想查看一下結果,而不是將他們聚合為一個值。先看collect的基礎方法,它接受三個參數:
<r>R collect(Supplier<r>supplier, BiConsumer<r,? t="" super="">accumulator, BiConsumer<r,r>combiner)
supplier:一個能創造目標類型實例的方法。accumulator:一個將當元素添加到目標中的方法。combiner:一個將中間狀態的多個結果整合到一起的方法(並發的時候會用到)。接着看代碼:</r,r></r,?></r></r>
Stream<integer>stream = Stream.of(1, 2, 3, 4).filter(p -> p > 2);
List<integer>result = stream.collect(() -> new ArrayList<>(), (list, item) -> list.add(item), (one, two) -> one.addAll(two));
/* 或者使用方法引用 */
result = stream.collect(ArrayList::new, List::add, List::addAll);
這個例子即為過濾大於2的元素,將剩余結果收集到一個新的list中。</integer></integer>
第一個方法生成一個新的ArrayList;
第二個方法中第一個參數是前面生成的ArrayList對象,第二個參數是stream中包含的元素,方法體就是把stream中的元素加入ArrayList對象中。第二個方法被反復調用直到原stream的元素被消費完畢;
第三個方法也是接受兩個參數,這兩個都是ArrayList類型的,方法體就是把第二個ArrayList全部加入到第一個中;
代碼有點繁瑣,或者使用collect的另一個重載方法:
<r,a>R collect(Collector collector)
注意到Collector其實是上面supplier、accumulator、combiner的聚合體。那么上面代碼就變成:</r,a>
List<integer>list = Stream.of(1, 2, 3, 4).filter(p -> p > 2).collect(Collectors.toList());
將結果收集到map中</integer>
先定義如下Person對象
class Person{
public String name;
public int age;
Person(String name, int age){
this.name = name;
this.age = age;
}
@Override
public String toString(){
return String.format("Person{name='%s', age=%d}", name, age);
}
}
假設你有一個Stream<person>對象,希望將其中元素收集到一個map中,這樣就可以根據他的名稱來查找對應年齡,例如:</person>
Map<string, integer="">result = people.collect(HashMap::new,(map,p)->map.put(p.name,p.age),Map::putAll);
/*使用Collectors.toMap形式*/
Map<string, integer="">result = people.collect(Collectors.toMap(p -> p.name, p -> p.age, (exsit, newv) -> newv));
其中Collectors.toMap方法的第三個參數為鍵值重復處理策略,如果不傳入第三個參數,當有相同的鍵時,會拋出一個IlleageStateException。</string,></string,>
或者你想將Person分解為Map存儲:
List<Map<string, object="">> personToMap = people.collect(ArrayList::new, (list, p) -> {
Map<string, object="">map = new HashMap<>();
map.put("name", p.name);
map.put("age", p.age);
list.add(map);
}, List::addAll);
分組和分片</string,></string,>
對具有相同特性的值進行分組是一個很常見的任務,Collectors提供了一個groupingBy方法,方法簽名為:
<t,k,a,d>Collector<T,?,Map<k,d>> groupingBy(Function classifier, Collector downstream)
classifier:一個獲取Stream元素中主鍵方法。downstream:一個操作對應分組后的結果的方法。</k,d></t,k,a,d>
假如要根據年齡來分組:
Map<Integer, List<person>> peropleByAge = people.filter(p -> p.age > 12).collect(Collectors.groupingBy(p -> p.age, Collectors.toList()));
假如我想要根據年齡分組,年齡對應的鍵值List存儲的為Person的姓名,怎么做呢:</person>
Map<Integer, List<string>> peropleByAge = people.collect(Collectors.groupingBy(p -> p.age, Collectors.mapping((Person p) -> p.name, Collectors.toList())));
mapping即為對各組進行投影操作,和Stream的map方法基本一致。</string>
假如要根據姓名分組,獲取每個姓名下人的年齡總和(好像需求有些坑爹):
Map<string, integer="">sumAgeByName = people.collect(Collectors.groupingBy(p -> p.name, Collectors.reducing(0, (Person p) -> p.age, Integer::sum)));
/* 或者使用summingInt方法 */
sumAgeByName = people.collect(Collectors.groupingBy(p -> p.name, Collectors.summingInt((Person p) -> p.age)));
可以看到Java8的分組功能相當強大,當然你還可以完成更復雜的功能。另外Collectors中還存在一個類似groupingBy的方法:partitioningBy,它們的區別是partitioningBy為鍵值為Boolean類型的groupingBy,這種情況下它比groupingBy更有效率。</string,>
join和統計功能
話說Java8中新增了一個StringJoiner,Collectors的join功能和它基本一樣。用於將流中字符串拼接並收集起來,使用很簡單:
String names = people.map(p->p.name).collect(Collectors.joining(","))
Collectors分別提供了求平均值averaging、總數couting、最小值minBy、最大值maxBy、求和suming等操作。但是假如你希望將流中結果聚合為一個總和、平均值、最大值、最小值,那么Collectors.summarizing(Int/Long/Double)就是為你准備的,它可以一次行獲取前面的所有結果,其返回值為(Int/Long/Double)SummaryStatistics。
DoubleSummaryStatistics dss = people.collect(Collectors.summarizingDouble((Person p)->p.age));
double average=dss.getAverage();
double max=dss.getMax();
double min=dss.getMin();
double sum=dss.getSum();
double count=dss.getCount();
轉自:http://www.jquerycn.cn/a_17583
