預備知識
Stream上的所有操作分為兩類:
中間操作和結束操作,中間操作只是一種標記,只有結束操作才會觸發(fā)實際計算。
中間操作又可以分為無狀態(tài)的和有狀態(tài)的:
無狀態(tài)中間操作是指元素的處理不受前面元素的影響,而有狀態(tài)的中間操作必須等到所有元素處理之后才知道最終結果,比如排序是有狀態(tài)操作,在讀取所有元素之前并不能確定排序結果;
結束操作又可以分為短路操作和非短路操作
短路操作是指不用處理全部元素就可以返回結果,比如找到第一個滿足條件的元素。之所以要進行如此精細的劃分,是因為底層對每一種情況的處理方式不同。
| 操作類型 | 狀態(tài)類型 | 方法 |
|---|---|---|
| 中間操作(Intermediate operations) | 無狀態(tài)(Stateless) | unordered() filter() map() mapToInt() mapToLong() mapToDouble() flatMap() flatMapToInt() flatMapToLong() flatMapToDouble() peek() |
| 中間操作(Intermediate operations) | 有狀態(tài)(Stateful) | distinct() sorted() sorted() limit() skip() |
| 結束操作(Terminal operations) | 非短路操作 | forEach() forEachOrdered() toArray() reduce() collect() max() min() count() |
| 結束操作(Terminal operations) | 短路操作(short-circuiting) | anyMatch() allMatch() noneMatch() findFirst() findAny() |
intermediate operation
map,mapToInt,...
map 對于流中的每個對象,對應輸出成另一個對象的流
mapToInt 輸出成IntStream
List<Cat> cats = Lists.newArrayList(
new Cat("咪咪", "中華田園", 10)
, new Cat("喵喵", "波斯貓", 5)
, new Cat("阿花", "暹羅", 3)
, new Cat("小花", "暹羅", 4)
);
List<String> names = cats.stream().map(Cat::getType).collect(Collectors.toList());
Integer maxAge = cats.stream().mapToInt(Cat::getAge).max().orElse(0);
// console
// [中華田園, 波斯貓, 暹羅, 暹羅]
// 10
flatMap,flatMapToInt
對于每個元素生成一個新的stream,并把它們扁平化(flat)

List<Integer> l1 = Lists.newArrayList(3, 5, 1, 2, 6);
List<Integer> l2 = Lists.newArrayList(4, 8, 7, 9, 0);
List<List<Integer>> l = Lists.newArrayList(l1, l2);
System.out.println(l);
System.out.println(l.stream().flatMap(Collection::stream).sorted().map(n -> n * n).collect(Collectors.toList()));
// console
[[3, 5, 1, 2, 6], [4, 8, 7, 9, 0]]
[0, 1, 4, 9, 16, 25, 36, 49, 64, 81]
peek
和map類似,但是map的參數(shù)是個帶返回值的function,peek是個不帶返回值的Consumer
這里有個坑,如果只使用peek而沒有后續(xù)操作的話,peek里面的方法不會被執(zhí)行??梢钥磒eek方法的apiNote
This method exists mainly to support debugging, where you want to see the elements as they flow past a certain point in a pipeline
List<Cat> cats = Lists.newArrayList(
new Cat("咪咪", "中華田園", 10)
, new Cat("喵喵", "波斯貓", 5)
, new Cat("阿花", "暹羅", 3)
, new Cat("小花", "暹羅", 4)
);
cats.stream().peek(System.out::println).count();
System.out.println("========================================");
cats.stream().peek(System.out::println);
System.out.println("----------------------------------------");
// console
// Cat(name=咪咪, type=中華田園, age=10)
// Cat(name=喵喵, type=波斯貓, age=5)
// Cat(name=阿花, type=暹羅, age=3)
// Cat(name=小花, type=暹羅, age=4)
// ========================================
// ----------------------------------------
filter
過濾出符合條件對象組成stream
List<Cat> cats = Lists.newArrayList(
new Cat("咪咪", "中華田園", 10)
, new Cat("喵喵", "波斯貓", 5)
, new Cat("阿花", "暹羅", 3)
, new Cat("小花", "暹羅", 4)
);
System.out.println(cats.stream().filter(n -> "波斯貓".equals(n.getType())).collect(Collectors.toList()));
// console
// [Cat(name=喵喵, type=波斯貓, age=5)]
distinct
去除相同的元素
List<Cat> cats = Lists.newArrayList(
new Cat("咪咪", "中華田園", 10)
, new Cat("喵喵", "波斯貓", 5)
, new Cat("阿花", "暹羅", 3)
, new Cat("小花", "暹羅", 4)
);
System.out.println(cats.stream().map(Cat::getType).distinct().collect(Collectors.toList()));
// console
// [中華田園, 波斯貓, 暹羅]
skip 跳過n個元素
limit 限制n個元素
List<Cat> cats = Lists.newArrayList(
new Cat("咪咪", "中華田園", 10)
, new Cat("喵喵", "波斯貓", 5)
, new Cat("阿花", "暹羅", 3)
, new Cat("小花", "暹羅", 4)
);
System.out.println(cats.stream().limit(3).collect(Collectors.toList()));
System.out.println(cats.stream().skip(2).collect(Collectors.toList()));
// [Cat(name=咪咪, type=中華田園, age=10), Cat(name=喵喵, type=波斯貓, age=5), Cat(name=阿花, type=暹羅, age=3)]
// [Cat(name=阿花, type=暹羅, age=3), Cat(name=小花, type=暹羅, age=4)]
sort 排序
List<Cat> cats = Lists.newArrayList(
new Cat("咪咪", "中華田園", 10)
, new Cat("喵喵", "波斯貓", 5)
, new Cat("阿花", "暹羅", 3)
, new Cat("小花", "暹羅", 4)
);
// System.out.println(cats.stream().sorted().collect(Collectors.toList())); // Cat 類沒有實現(xiàn)Comparable接扣,會報錯
System.out.println(cats.stream().sorted(Comparator.comparing(Cat::getAge)).collect(Collectors.toList()));
System.out.println(cats.stream().sorted(Comparator.comparing(Cat::getAge).reversed()).collect(Collectors.toList()));
// console
// [Cat(name=阿花, type=暹羅, age=3), Cat(name=小花, type=暹羅, age=4), Cat(name=喵喵, type=波斯貓, age=5), Cat(name=咪咪, type=中華田園, age=10)]
// [Cat(name=咪咪, type=中華田園, age=10), Cat(name=喵喵, type=波斯貓, age=5), Cat(name=小花, type=暹羅, age=4), Cat(name=阿花, type=暹羅, age=3)]
unordered
用來消除遇到順序
不建議使用
If a stream is ordered, repeated execution of identical stream pipelines on an identical source will produce an identical result; if it is not ordered, repeated execution might produce different results.
遇到順序(encounter order)
https://www.ibm.com/developerworks/cn/java/j-java-streams-3-brian-goetz/index.html
遇到順序
另一個影響庫的優(yōu)化能力的微妙的考慮事項是遇到順序。遇到順序指的是來源分發(fā)元素的順序是否對計算至關重要。一些來源(比如基于哈希的集合和映射)沒有有意義的遇到順序。流標志 ORDERED 描述了流是否有有意義的遇到順序。JDK 集合的 spliterator 會根據(jù)集合的規(guī)范來設置此標志;一些中間操作可能注入 ORDERED (sorted()) 或清除它 (unordered())。
如果流沒有遇到順序,大部分流操作都必須遵守該順序。對于順序執(zhí)行,會自動保留遇到順序,因為元素會按遇到它們的順序自然地處理。甚至在并行執(zhí)行中,許多操作(無狀態(tài)中間操作和一些終止操作(比如 reduce())),遵守遇到順序不會產(chǎn)生任何實際成本。但對于其他操作(有狀態(tài)中間操作,其語義與遇到順序關聯(lián)的終止操作,比如 findFirst() 或 forEachOrdered()),在并行執(zhí)行中遵守遇到順序的責任可能很重大。如果流有一個已定義的遇到順序,但該順序對結果沒有意義,那么可以通過使用 unordered() 操作刪除 ORDERED 標志,加速包含順序敏感型操作的管道的順序執(zhí)行。
作為對遇到順序敏感的操作的示例,可以考慮 limit(),它會在指定大小處截斷一個流。在順序執(zhí)行中實現(xiàn) limit() 很簡單:保留一個已看到多少元素的計數(shù)器,在這之后丟棄任何元素。但是在并行執(zhí)行中,實現(xiàn) limit() 要復雜得多;您需要保留前 N 個元素。此要求大大限制了利用并行性的能力;如果輸入劃分為多個部分,您只有在某個部分之前的所有部分都已完成后,才知道該部分的結果是否將包含在最終結果中。因此,該實現(xiàn)一般會錯誤地選擇不使用所有可用的核心,或者緩存整個試驗性結果,直到您達到目標長度。
如果流沒有遇到順序,limit() 操作可以自由選擇任何 N 個元素,這讓執(zhí)行效率變得高得多。知道元素后可立即將其發(fā)往下游,無需任何緩存,而且線程之間唯一需要執(zhí)行的協(xié)調是發(fā)送一個信號來確保未超出目標流長度。
遇到順序成本的另一個不太常見的示例是排序。如果遇到順序有意義,那么 sorted() 操作會實現(xiàn)一種穩(wěn)定 排序(相同的元素按照它們進入輸入時的相同順序出現(xiàn)在輸出中),而對于無序的流,穩(wěn)定性(具有成本)不是必需的。distinct() 具有類似的情況:如果流有一個遇到順序,那么對于多個相同的輸入元素,distinct() 必須發(fā)出其中的第一個,而對于無序的流,它可以發(fā)出任何元素 — 同樣可以獲得高效得多的并行實現(xiàn)。
在您使用 collect() 聚合時會遇到類似的情形。如果在無序流上執(zhí)行 collect(groupingBy()) 操作,與任何鍵對應的元素都必須按它們在輸入中出現(xiàn)的順序提供給下游收集器。此順序對應用程序通常沒有什么意義,而且任何順序都沒有意義。在這些情況下,可能最好選擇一個并發(fā) 收集器(比如 groupingByConcurrent()),它可以忽略遇到順序,并讓所有線程直接收集到一個共享的并發(fā)數(shù)據(jù)結構中(比如 ConcurrentHashMap),而不是讓每個線程收集到它自己的中間映射中,然后再合并中間映射(這可能產(chǎn)生很高的成本)。
Set<Integer> l = new TreeSet<>();
l.add(1);
l.add(10);
l.add(3);
l.add(-3);
l.add(-4);
System.out.println("Serial Stream");
l.stream().map(s->s+" ").forEach(System.out::print);
System.out.println();
l.stream().unordered().map(s->s+" ").forEach(System.out::print);
System.out.println("\n");
System.out.println("Unordered Operations on a Parallel Stream");
l.stream().parallel().map(s->s+" ").forEach(System.out::print);
System.out.println();
l.stream().unordered().map(s->s+" ").parallel().forEach(System.out::print);
System.out.println("\n");
System.out.println("Ordered Operations on a Parallel Stream");
l.stream().parallel().skip(2).limit(2).findFirst().ifPresent(System.out::print);
System.out.println();
l.stream().unordered().parallel().skip(2).limit(2).findFirst().ifPresent(System.out::print);
System.out.println("\n");
// console
Serial Stream
-4 -3 1 3 10
-4 -3 1 3 10
Unordered Operations on a Parallel Stream
3 10 -3 1 -4
3 10 -4 -3 1
Ordered Operations on a Parallel Stream
1
-3
allMatch 任意一個符合,返回true
anyMatch 所有的都符合,返回true
noneMatch 所有的都不符合,返回true
List<Cat> cats = Lists.newArrayList(
new Cat("咪咪", "中華田園", 10)
, new Cat("喵喵", "波斯貓", 5)
, new Cat("阿花", "暹羅", 3)
, new Cat("小花", "暹羅", 4)
);
System.out.println(cats.stream().allMatch(n -> "波斯貓".equals(n.getType())));
System.out.println(cats.stream().anyMatch(n -> "波斯貓".equals(n.getType())));
System.out.println(cats.stream().noneMatch(n -> "波斯貓".equals(n.getType())));
System.out.println(cats.stream().allMatch(n -> n.getName().length() == 2));
System.out.println(cats.stream().anyMatch(n -> n.getName().length() == 2));
System.out.println(cats.stream().noneMatch(n -> n.getName().length() == 2));
System.out.println(cats.stream().allMatch(n -> n.getAge() > 12));
System.out.println(cats.stream().anyMatch(n -> n.getAge() > 12));
System.out.println(cats.stream().noneMatch(n -> n.getAge() > 12));
// console
// false
// true
// false
// true
// true
// false
// false
// false
// true
onClose
流關閉的時候可以執(zhí)行一個Runnable
Stream<Integer> stream = Lists.newArrayList(1, 2, 3, 4, 5).stream();
stream.onClose(() -> {
System.out.println("stream is closed");
});
List<Integer> collect = stream.map(n -> n * n).collect(Collectors.toList());
System.out.println(collect);
stream.close();
// concole
[1, 4, 9, 16, 25]
stream is closed
parallel 并行流
sequential 串行流
terminal operation
findFirst,findAny
findFirst返回一個第一個對象的Optional對象
findAny返回任意一個對象的Optional對象
在stream里兩個方法里面沒有區(qū)別,都是返回第一個對象
在parallelStream里面any是真的any
@Data
@AllArgsConstructor
public class Cat {
private String name;
private String type;
private int age;
}
List<Cat> cats = Lists.newArrayList(
new Cat("咪咪", "中華田園", 10)
, new Cat("喵喵", "波斯貓", 5)
, new Cat("阿花", "暹羅貓", 3));
System.out.println(cats.stream().findFirst());
System.out.println(cats.parallelStream().findFirst());
System.out.println(cats.stream().findAny());
System.out.println(cats.parallelStream().findAny()); // 每次執(zhí)行結果不一定
// console
// Optional[Cat(name=咪咪, type=中華田園, age=10)]
// Optional[Cat(name=咪咪, type=中華田園, age=10)]
// Optional[Cat(name=咪咪, type=中華田園, age=10)]
// Optional[Cat(name=喵喵, type=波斯貓, age=5)]
min,max,count
List<Cat> cats = Lists.newArrayList(
new Cat("咪咪", "中華田園", 10)
, new Cat("喵喵", "波斯貓", 5)
, new Cat("阿花", "暹羅", 3));
System.out.println(cats.stream().min(Comparator.comparingInt(Cat::getAge)));
System.out.println(cats.stream().max(Comparator.comparingInt(c -> c.getType().length())));
System.out.println(cats.stream().filter(n -> n.getAge() < 5).count());
// console
// Optional[Cat(name=阿花, type=暹羅, age=3)]
// Optional[Cat(name=咪咪, type=中華田園, age=10)]
// 1
forEach,forEachOrdered
遍歷元素,在串行遍歷的時候,沒有區(qū)別,在并行遍歷的時候,forEach不能保證順序
cList<Integer> l = Lists.newArrayList(3, 5, 2, 4, 1);
l.stream().forEach(System.out::print);
System.out.println();
l.stream().forEachOrdered(System.out::print);
System.out.println();
l.stream().parallel().forEach(System.out::print);
System.out.println();
l.stream().parallel().forEachOrdered(System.out::print);
// console
35241
35241
25341 // 每次執(zhí)行結果有可能不一樣
35241
toArray
把stream轉換成一個數(shù)組
Cat c1 = new Cat("咪咪", "中華田園", 10);
Cat c2 = new Cat("喵喵", "波斯貓", 5);
Cat c3 = new Cat("阿花", "暹羅", 3);
Cat c4 = new Cat("小花", "暹羅", 4);
Cat c5 = new Cat("小白", "藍貓", 6);
Cat c6 = new Cat("小黑", "美短", 15);
Cat c7 = new Cat("小黃", "英短", 1);
Cat c8 = new Cat("小粉", "暹羅", 1);
List<Cat> cats = Lists.newArrayList(
c1, c2, c3, c4, c5, c6, c7, c8
);
Object[] objects = cats.stream().map(Cat::getAge).toArray();
for (Object object : objects) {
System.out.print(object + " ");
}
System.out.println();
Integer[] ages = cats.stream().map(Cat::getAge).toArray(Integer[]::new);
for (Integer age : ages) {
System.out.print(age + " ");
}
// console
10 5 3 4 6 15 1 1
10 5 3 4 6 15 1 1
reduce 累加操作
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6);
List<String> strs = Lists.newArrayList("a", "b", "c", "d", "e");
int result1 = numbers.stream().reduce(100, (subtotal, element) -> subtotal + element);
String result2 = strs.stream().reduce((result, word) -> result + "_" + word).orElse("");
System.out.println(result1);
System.out.println(result2);
int result3 = numbers.stream().reduce((subtotal, element) -> subtotal + element).orElse(0);
String result4 = strs.stream().reduce("start", (result, word) -> result + "_" + word);
System.out.println(result3);
System.out.println(result4);
// console
121
a_b_c_d_e
21
start_a_b_c_d_e
collect 結果收集
Cat c1 = new Cat("咪咪", "中華田園", 10);
Cat c2 = new Cat("喵喵", "波斯貓", 5);
Cat c3 = new Cat("阿花", "暹羅", 3);
Cat c4 = new Cat("小花", "暹羅", 4);
List<Cat> cats = Lists.newArrayList(
c1, c2, c3, c4
);
// toList
List<String> collect = cats.stream().map(Cat::getName).collect(Collectors.toList());
System.out.println("toList:" + collect);
// toSet
Set<String> set = cats.stream().map(Cat::getType).collect(Collectors.toSet());
System.out.println("toSet:" + set);
// toMap
Map<String, Cat> map = cats.stream().collect(Collectors.toMap(Cat::getName, n -> n));
System.out.println("toMap:" + map);
// 會報錯提示 Duplicate key,key不能重復
// Map<String, Cat> map2 = cats.stream().collect(Collectors.toMap(Cat::getType, n -> n));
// System.out.println(map2);
// groupBy
Map<String, List<Cat>> groupBy = cats.stream().collect(Collectors.groupingBy(Cat::getType));
System.out.println("groupBy:" + groupBy);
// partitioningBy
Map<Boolean, List<Cat>> partitioningBy = cats.stream().collect(Collectors.partitioningBy(n -> n.getAge() > 5));
System.out.println("partitioningBy:" + partitioningBy);
// console
toList:[咪咪, 喵喵, 阿花, 小花]
toSet:[暹羅, 波斯貓, 中華田園]
toMap:{阿花=Cat(name=阿花, type=暹羅, age=3), 小花=Cat(name=小花, type=暹羅, age=4), 喵喵=Cat(name=喵喵, type=波斯貓, age=5), 咪咪=Cat(name=咪咪, type=中華田園, age=10)}
groupBy:{暹羅=[Cat(name=阿花, type=暹羅, age=3), Cat(name=小花, type=暹羅, age=4)], 波斯貓=[Cat(name=喵喵, type=波斯貓, age=5)], 中華田園=[Cat(name=咪咪, type=中華田園, age=10)]}
partitioningBy:{false=[Cat(name=喵喵, type=波斯貓, age=5), Cat(name=阿花, type=暹羅, age=3), Cat(name=小花, type=暹羅, age=4)], true=[Cat(name=咪咪, type=中華田園, age=10)]}