為什么需要Stream
Java 8 中的Stream是對(duì)集合 (Collection) 對(duì)象功能的增強(qiáng), 他專(zhuān)注于對(duì)集合對(duì)象進(jìn)行各種非常便利,高效的聚合操作(aggregate operation), 或者大批量數(shù)據(jù)操作 (bulk data operation).
Stream API借助于同樣新出現(xiàn)的Lambda 表達(dá)式, 極大的提高編程效率和程序可讀性. 同時(shí)他提供穿行和并行兩種模式進(jìn)行匯聚操作, 并發(fā)模式能夠成分利用多核處理器的優(yōu)勢(shì), 使用fork/join 并行法師來(lái)拆分任務(wù)和加速處理過(guò)程.
通常編寫(xiě)并行代碼很難而且容易出錯(cuò), 但使用Steam API無(wú)需編寫(xiě)一行多線程的代碼, 就可以很方便地寫(xiě)出高性能的并發(fā)代碼睬涧。
幾個(gè)例子:
1. Java 8的排序募胃,取值實(shí)現(xiàn)
List<Integer> transactionsIds =
transactions.stream().filter(t -> t.getType() == Transaction.Type.GEOCERY)
.sorted(Comparator.comparing(Transaction::getValue).reversed())//排序
.map(Transaction::getId)//取出id組裝新stream
.collect(Collectors.toList());
System.out.println(transactionsIds);//[6, 5, 3, 1]
2. 流轉(zhuǎn)換為其他數(shù)據(jù)結(jié)構(gòu)(collect)
一個(gè)Stream只可以使用一次,否則會(huì)報(bào)錯(cuò)
Stream stream = Stream.of("a1", "b1", "c1");
// 1. Array
String[] strArray1 = (String[]) stream.toArray(String[]::new);
for (String s : strArray1) { System.out.print(s); } //a1b1c1
// 2.Collection list
stream = Stream.of("a1", "b1", "c1");// stream has already been operated upon or closed
List<String> list1 = (List<String>) stream.collect(Collectors.toList());
for (String s : list1) { System.out.print(s); }//a1b1c1
// 2.Collection list
stream = Stream.of("a1", "b1", "c1");// stream has already been operated upon or closed
List<String> list2 = (List<String>) stream.collect(Collectors.toCollection(ArrayList::new));
for (String s : list2) { System.out.print(s); } //a1b1c1
// 2.Collection set
stream = Stream.of("a1", "b1", "c1");// stream has already been operated upon or closed
Set<String> set = (Set<String>) stream.collect(Collectors.toSet());
for (String s : set) { System.out.print(s); } //a1c1b1
// 2.Collection stack
stream = Stream.of("a1", "b1", "c1");// stream has already been operated upon or closed
Stack<String> stack = (Stack<String>) stream.collect(Collectors.toCollection(Stack::new));
for (String s : stack) { System.out.print(s); } //a1b1c1
// 3. String
stream = Stream.of("a1", "b1", "c1");// stream has already been operated upon or closed
String str = stream.collect(Collectors.joining()).toString();
System.out.print(str); // a1b1c1
String str = stream.collect(Collectors.joining("&")).toString();
System.out.print(str); // a1&b1&c1
String str = stream.collect(Collectors.joining("&","prefix","suffix")).toString();
System.out.print(str); // prefixa1&b1&c1suffix
//4. stream元素?cái)?shù)量
Object str = stream.collect(Collectors.counting());
System.out.print(str); // 3
//5. groupby
Object str = stream.collect(Collectors.groupingBy(o -> o));
System.out.print(str); // {a1=[a1], c1=[c1], b1=[b1]}
Stream stream = Stream.of("a1", "a1", "b1", "c1");// 分組求和
Object str = stream.collect(Collectors.groupingBy(Function.identity(), Collectors.counting()));
System.out.print(str); // {a1=2, c1=1, b1=1}
//自定義對(duì)象分組求和
Map<String, Integer> sumMap = fruitList.stream().collect.
(Collectors.groupingBy(Fruit::getName, Collectors.summingInt(Fruit::getPrice)));
3. 打印姓名 (forEach 和pre-java8的對(duì)比)
forEach 不能修改自己包含的本地變量值畦浓,也不能用break/return 之類(lèi)的關(guān)鍵字提前結(jié)束循環(huán)痹束,只能進(jìn)行全部值迭代后處理
// JDK 8
roster.stream().filter(p -> p.gender == Person.Sex.MALE)
.forEach(p -> System.out.println(p.name));
// JDK 7
for (Person p : roster) {
if(p.gender == Person.Sex.MALE){
System.out.println(p.name);
}
}
4. peek 對(duì)每個(gè)元素執(zhí)行操作并且返回一個(gè)新的Stream
【peek : 偷窺】注意執(zhí)行順序
Stream.of("one", "two", "three", "four")
.filter(p -> p.length() > 3)
.peek(v -> System.out.println("Filtered Value:" + v))
.map(String::toUpperCase)
.peek(v -> System.out.println("Mapped Value:" + v))
.collect(Collectors.toList());
// 1. Filtered Value:three
// 2. Mapped Value:THREE
// 3. Filtered Value:four
// 4. Mapped Value:FOUR
5.reduce的用例
使用方式說(shuō)明
- 方式一
一個(gè)參數(shù),reduce(BinaryOperator<T> accumulator)
方法需要一個(gè)函數(shù)式接口參數(shù)讶请,該函數(shù)式接口需要兩個(gè)參數(shù)祷嘶,返回一個(gè)結(jié)果(reduce中返回的結(jié)果會(huì)作為下次累加器計(jì)算的第一個(gè)參數(shù)),也就是累加器秽梅。 - 方式二
2個(gè)參數(shù),T reduce(T identity, BinaryOperator<T> accumulator)
提供一個(gè)跟Stream中數(shù)據(jù)同類(lèi)型的初始值identity剿牺,通過(guò)累加器accumulator迭代計(jì)算Stream中的數(shù)據(jù)企垦,得到一個(gè)跟Stream中數(shù)據(jù)相同類(lèi)型的最終結(jié)果。 - 方式三
3個(gè)參數(shù)晒来,<U> U reduce(U identity, BiFunction<U, ? super T, U> accumulator, BinaryOperator<U> combiner);
reduce的第三個(gè)參數(shù)是在使用parallelStream的reduce操作時(shí)钞诡,合并各個(gè)流結(jié)果的,如果使用的是stream湃崩,所以第三個(gè)參數(shù)是不起作用的荧降。
一或者二參數(shù)的使用示例
// 1. 求和 SUM 10
Integer sum = Stream.of(1, 2, 3, 4).reduce(0, (a, b) -> a + b);
sum = Stream.of(1, 2, 3, 4).reduce(0, Integer::sum); //有起始值
sum = Stream.of(1, 2, 3, 4).reduce(Integer::sum).get(); //無(wú)起始值
// 2. 最小值 minValue = -3.0
double minValue = Stream.of(-1.5, 1.0, -3.0, -2.0).reduce(Double.MAX_VALUE, Double::min);
minValue = Stream.of(-1.5, 1.0, -3.0, -2.0).reduce(Double::min).get();
// 2. 最大數(shù)值 maxValue = 1.0
double maxValue = Stream.of(-1.5, 1.0, -3.0, -2.0).reduce(Double.MIN_VALUE, Double::max);
maxValue = Stream.of(-1.5, 1.0, -3.0, -2.0).reduce(Double::max).get();
// 3. 字符串連接 Concat "ABCD"
String concat = Stream.of("A", "B", "C", "D").reduce("", String::concat);
// 4. 過(guò)濾和字符串連接 Filter & Concat = "ace"
concat = Stream.of("a", "B", "c", "D", "e", "F")
.filter(x -> x.compareTo("Z") > 0)
.reduce("", String::concat);
//5. Stringbuffer連接
StringBuffer reduce = target.entrySet().stream().sorted(Comparator.comparing(Map.Entry::getKey)).
reduce(new StringBuffer(),
(stringBuffer, stringEntry) -> stringBuffer.append(stringEntry.getKey()).append("=").append(stringEntry.getValue().getClass()).append("&"),
StringBuffer::append);
三個(gè)參數(shù)進(jìn)行并行計(jì)算reduce高級(jí)用法
需要排序的功能一定不要用并行計(jì)算,因?yàn)椴⑿杏?jì)算為多線程攒读,返回結(jié)果進(jìn)行combine后是亂序的朵诫。
基本用法示例
當(dāng)Stream是并行時(shí),第三個(gè)參數(shù)就有意義了薄扁,它會(huì)將不同線程計(jì)算的結(jié)果調(diào)用combiner做匯總后返回剪返。
注意由于采用了并行計(jì)算废累,前兩個(gè)參數(shù)與非并行時(shí)也有了差異!第一個(gè)參數(shù)如果是并行運(yùn)算脱盲,那么每個(gè)線程都是從這個(gè)初始值開(kāi)始運(yùn)算邑滨,如果初始值不是0或者“”那么與單線程的初始值就有差異了。
舉個(gè)簡(jiǎn)單點(diǎn)的例子钱反,計(jì)算4+1+2+3的結(jié)果掖看,其中4是初始值:
Integer reduce = Stream.of(1, 2, 3).parallel().reduce(4, (integer, integer2) -> integer + integer2
, new BinaryOperator<Integer>() {
@Override
public Integer apply(Integer integer, Integer integer2) {
return integer + integer2;
}
});
System.out.println("reduce = " + reduce);
并行時(shí)的計(jì)算結(jié)果是18,而非并行時(shí)的計(jì)算結(jié)果是10面哥!
為什么會(huì)這樣哎壳?
先分析下非并行時(shí)的計(jì)算過(guò)程;第一步計(jì)算4 + 1 = 5幢竹,第二步是5 + 2 = 7耳峦,第三步是7 + 3 = 10。
并行計(jì)算時(shí)焕毫,線程之間沒(méi)有影響蹲坷,因此每個(gè)線程在調(diào)用第二個(gè)參數(shù)BiFunction進(jìn)行計(jì)算時(shí),直接都是使用result值當(dāng)其第一個(gè)參數(shù)邑飒,因此計(jì)算過(guò)程現(xiàn)在是這樣的:線程1:1 + 4 = 5循签;線程2:2 + 4 = 6;線程3:3 + 4 = 7疙咸;Combiner函數(shù): 5 + 6 + 7 = 18县匠!
如果初始值是0,那么并行計(jì)算和非并行是結(jié)果一樣的撒轮。
Integer reduce = Stream.of(1, 2, 3).parallel().reduce(0, (integer, integer2) -> integer + integer2
, new BinaryOperator<Integer>() {
@Override
public Integer apply(Integer integer, Integer integer2) {
return integer + integer2;
}
});
System.out.println("reduce = " + reduce);
輸出reduce = 6
特殊操作示例
- 轉(zhuǎn)換成List乞旦,當(dāng)然最簡(jiǎn)單的是
collect(Collectors.toList());
說(shuō)明:如果第一個(gè)參數(shù)的類(lèi)型是ArrayList等對(duì)象而非基本數(shù)據(jù)類(lèi)型的包裝類(lèi)或者String,第三個(gè)函數(shù)的處理上可能容易引起誤解题山。下面第三個(gè)參數(shù)
(l, s) -> l
的入?yún)和s一樣都是第一個(gè)參數(shù)list兰粉,所以這里只返回了l。
按照第三個(gè)參數(shù)作用是:它會(huì)將不同線程計(jì)算的結(jié)果調(diào)用combiner做匯總后返回顶瞳。那就會(huì)這樣寫(xiě)l.addAll(s);
這個(gè)時(shí)候出來(lái)的結(jié)果與預(yù)期的結(jié)果就完全不一樣了玖姑,要多了很多元素!
//parallelStream的并行流慨菱,是多線程的操作焰络,結(jié)果不是按照流的數(shù)據(jù)順序是亂序的。
ArrayList<String> reduce1 = target.entrySet().parallelStream().map(stringEntry -> stringEntry.getKey()).reduce(new ArrayList<String>(), (l, s) -> {
l.add(s);
return l;
}, (l, s) -> l);
System.out.println("reduce1 = " + reduce1);
輸出reduce1 = [SELF-TASK, SELF, OTHER-TASK, OTHER]
- 第三個(gè)參數(shù)是把各個(gè)流結(jié)果合并為null符喝,由于是parallelStream所以規(guī)則起作用闪彼,返回null。
//第三個(gè)參數(shù)是把各個(gè)流結(jié)果合并為null协饲,由于是parallelStream所以規(guī)則起作用备蚓,返回null课蔬。
String reduce2 = target.entrySet().parallelStream().map(stringEntry -> stringEntry.getKey()).reduce("", (r, s) -> r.concat(s).concat(","), (r, s) -> null);
System.out.println("reduce2 = " + reduce2);
輸出reduce2 = null
- 第三個(gè)參數(shù)是把各個(gè)流結(jié)果合并為null,由于是stream不是parallelStream所以規(guī)則不起作用郊尝。
//第三個(gè)參數(shù)是把各個(gè)流結(jié)果合并為null二跋,由于是stream不是parallelStream所以規(guī)則不起作用。
val reduce5 = target.entrySet().stream().map(stringEntry -> stringEntry.getKey()).reduce("", (r, s) -> r.concat(s), (r, s) -> null);
System.out.println("reduce5 = " + reduce5);
輸出reduce5 = OTHERSELF-TASKSELFOTHER-TASK
- 初始值為"",進(jìn)行字符串連接流昏,然后進(jìn)行parallelStream()各個(gè)流的再連接扎即。parallelStream所以出來(lái)的結(jié)果是亂序的。
//初始值為"",進(jìn)行字符串連接况凉,然后進(jìn)行parallelStream()各個(gè)流的再連接
String reduce4 = target.entrySet().parallelStream().map(stringEntry -> stringEntry.getKey()).reduce("", (r, s) -> r.concat(s).concat(","), String::concat);
System.out.println("reduce4 = " + reduce4);
輸出reduce4 = OTHER,SELF-TASK,SELF,OTHER-TASK,
- 簡(jiǎn)化版的字符串連接示例
//使用一個(gè)參數(shù)簡(jiǎn)化版的字符串連接谚鄙。
val reduce3 = target.entrySet().parallelStream().map(stringEntry -> stringEntry.getKey()).reduce(String::concat);
System.out.println("reduce3 = " + reduce3);
輸出reduce3 = Optional[OTHERSELF-TASKSELFOTHER-TASK]
- 迭代stream里面是否包含字符串“SELF”,主要演示在并行流情況下刁绒,reduce處理boolean型數(shù)據(jù)
//迭代stream里面是否包含字符串“SELF”
boolean flag = false;
Boolean f1 = target.entrySet().parallelStream().map(stringEntry -> stringEntry.getKey()).peek(s -> System.out.println("當(dāng)前值:" + s + "闷营,處理的線程:" + Thread.currentThread().getName())).reduce(flag, (f, s) -> {
f = Boolean.logicalOr(f, s.contains("SELF"));
System.out.println("當(dāng)前boolean 值f = " + f + ",處理的線程:" + Thread.currentThread().getName());
return f;
}, Boolean::logicalOr);
System.out.println("f1 = " + f1);
- 第一次運(yùn)行知市,輸出
當(dāng)前值:SELF-TASK傻盟,處理的線程:restartedMain
當(dāng)前值:OTHER-TASK,處理的線程:ForkJoinPool.commonPool-worker-2
當(dāng)前boolean 值f = true嫂丙,處理的線程:restartedMain
當(dāng)前值:OTHER娘赴,處理的線程:ForkJoinPool.commonPool-worker-1
當(dāng)前值:SELF,處理的線程:restartedMain
當(dāng)前boolean 值f = true跟啤,處理的線程:restartedMain
當(dāng)前boolean 值f = true诽表,處理的線程:ForkJoinPool.commonPool-worker-2
當(dāng)前boolean 值f = true,處理的線程:ForkJoinPool.commonPool-worker-1
f1 = true
- 再一次運(yùn)行隅肥,由于是多線程竿奏,輸出不一樣,但是最后的結(jié)果f1是一樣的腥放。
當(dāng)前值:SELF-TASK泛啸,處理的線程:restartedMain
當(dāng)前boolean 值f = true,處理的線程:restartedMain
當(dāng)前值:SELF捉片,處理的線程:restartedMain
當(dāng)前值:OTHER平痰,處理的線程:ForkJoinPool.commonPool-worker-2
當(dāng)前boolean 值f = true汞舱,處理的線程:restartedMain
當(dāng)前值:OTHER-TASK伍纫,處理的線程:ForkJoinPool.commonPool-worker-1
當(dāng)前boolean 值f = false,處理的線程:ForkJoinPool.commonPool-worker-1
當(dāng)前boolean 值f = false昂芜,處理的線程:ForkJoinPool.commonPool-worker-2
f1 = true
6. 我的一個(gè)實(shí)踐
這個(gè)功能是使用在我微信小程序的后端接口莹规,實(shí)現(xiàn)通過(guò)搜索名字來(lái)展示開(kāi)發(fā)語(yǔ)言列表。
原代碼
List<String> filterList = new ArrayList<String>();
languageList.forEach(c -> {
if (c.toLowerCase().contains(language_name.toLowerCase()))
filterList.add("guo"+c);
});
使用Stream后的代碼
List<String> filterList = languageList.stream().
filter(s -> s.toLowerCase().contains(language_name.toLowerCase())).
map("guo"::concat).
collect(Collectors.toList());
一樣的使用效果
訪問(wèn):https://localhost:8888/v2/vue/api/programLanguage/getByName?language_name=c
返回:["C","C++"]
幾個(gè)重要示例
List的排序泌神、過(guò)濾良漱、peek輸出
@Autowried
List<? extends IBankRouterHandler> source;
List<? extends IBankRouterHandler> collect = source.stream().
filter(bankHandler -> StringUtils.isNotEmpty(bankHandler.getRouterChannel())).
sorted(Comparator.comparing(bankHandler -> bankHandler.getRouterChannel())).
peek(bankHandler -> System.out.println("class為 = " + bankHandler.getRouterChannel())).
collect(Collectors.toList());
List轉(zhuǎn)Map(把對(duì)象的channel屬性作為key舞虱,對(duì)象本身最為value)
Map<String, ? extends IBankRouterHandler> target = source.stream().
filter(bankHandler -> StringUtils.isNotEmpty(bankHandler.getRouterChannel())).
sorted(Comparator.comparing(bankHandler -> bankHandler.getRouterChannel())).
peek(bankHandler -> System.out.println("class為 = " + bankHandler.getRouterChannel())).
collect(Collectors.toMap(IBankRouterHandler::getRouterChannel, s -> s));//兩個(gè)參數(shù)分別為Map的key和value。把對(duì)象的channel作為key母市,對(duì)象本身最為value矾兜。
Map排序
Map<String, ? extends IBankRouterHandler> target;
target.entrySet().stream().sorted(Comparator.comparing(Map.Entry::getKey)).collect(Collectors.toMap(stringEntry -> stringEntry.getKey(), stringEntry -> stringEntry.getValue()));
Map轉(zhuǎn)List
target.entrySet().stream().sorted(Comparator.comparing(Map.Entry::getKey)).map(s -> s.getValue()).collect(Collectors.toList());
forEach
StringBuffer params = new StringBuffer();
target.entrySet().stream().sorted(Comparator.comparing(Map.Entry::getKey)).forEach((s) -> params.append(s.getKey() + "=" + s.getValue().getClass() + "&"));
System.out.println("params = " + params.toString());
上面forEach的reduce(StringBuffer)等價(jià)實(shí)現(xiàn)
StringBuffer reduce = target.entrySet().stream().sorted(Comparator.comparing(Map.Entry::getKey)).reduce(new StringBuffer(), (stringBuffer, stringEntry) -> stringBuffer.append(stringEntry.getKey()).append("=").append(stringEntry.getValue().getClass()).append("&"), StringBuffer::append);
System.out.println("paramsreduce = " + reduce.toString());
上面forEach的reduce(String)等價(jià)實(shí)現(xiàn)
String reduce1 = target.entrySet().stream().map(stringEntry -> stringEntry.getKey()).filter(key -> key.length() < 11).sorted(Comparator.comparing(s -> s)).reduce("", (s, s2) -> s.concat(s2).concat("&"), String::concat);
上面forEach的collector方法等價(jià)實(shí)現(xiàn)【更簡(jiǎn)單,推薦方法】
Map<String, String> params = new HashMap<String, String>();
params.put("age", "12");
params.put("location", "天津");
params.put("zoo", "cat");
params.put("name", "guo");
params.put("go", "school");
String collect1 = params.entrySet().stream().sorted(Comparator.comparing(Map.Entry::getKey)).map(entry -> entry.getKey() + "=" + entry.getValue()).collect(Collectors.joining("&"));
System.out.println("collect1 = " + collect1);
輸出:collect1 = age=12&go=school&location=天津&name=guo&zoo=cat
自定義比較器在Stream的max患久、min方法
Optional<? extends IBankRouterHandler> max = target.entrySet().stream().map(stringEntry -> stringEntry.getValue()).max((o1, o2) -> o1.getRouterChannel().compareTo(o2.getRouterChannel()));