Java 二十二篇:Stream流
操作符
什么是操作符呢?操作符就是對數(shù)據(jù)進(jìn)行的一種處理工作,一道加工程序;就好像工廠的工人對流水線上的產(chǎn)品進(jìn)行一道加工程序一樣。

Stream流
stream就是同一個迭代器,單向,不可往復(fù),數(shù)據(jù)只能遍歷一次,遍歷一次后即用盡了
生成Stream Source
從Collection和數(shù)組
Collection.stream()
Colleaction.parallSteam()
Arrays.stream(T array) or Stream.of()
從BufferedReader
java.io.BufferedReader.lines()
靜態(tài)工廠
java.util.stream.IntStream.range()
java.nio.file.Files.walk()
自己構(gòu)建
java.util.Spliterator
其他
Random.ints()
BitSet.stream()
Pattern.splitAsStream(java.lang.CharSequence)
JarFile.stream()

流(Stream)的操作類型分為兩種:
中間操作符
對于數(shù)據(jù)流來說,中間操作符在執(zhí)行制定處理程序后,數(shù)據(jù)流依然可以傳遞給下一級的操作符。
中間操作符包含8種(排除了parallel,sequential,這兩個操作并不涉及到對數(shù)據(jù)流的加工操作):
● map(mapToInt,mapToLong,mapToDouble) 轉(zhuǎn)換操作符,把比如A->B,這里默認(rèn)提供了轉(zhuǎn)int,long,double的操作符。
● flatmap(flatmapToInt,flatmapToLong,flatmapToDouble) 拍平操作比如把 int[]{2,3,4} 拍平 變成 2,3,4 也就是從原來的一個數(shù)據(jù)變成了3個數(shù)據(jù),這里默認(rèn)提供了拍平成int,long,double的操作符。
● limit 限流操作,比如數(shù)據(jù)流中有10個 我只要出前3個就可以使用。
● distint 去重操作,對重復(fù)元素去重,底層使用了equals方法。
● filter 過濾操作,把不想要的數(shù)據(jù)過濾。
● peek 挑出操作,如果想對數(shù)據(jù)進(jìn)行某些操作,如:讀取、編輯修改等。
● skip 跳過操作,跳過某些元素。
● sorted(unordered) 排序操作,對元素排序,前提是實現(xiàn)Comparable接口,當(dāng)然也可以自定義比較器。
終止操作符
數(shù)據(jù)經(jīng)過中間加工操作,就輪到終止操作符上場了;終止操作符就是用來對數(shù)據(jù)進(jìn)行收集或者消費的,數(shù)據(jù)到了終止操作這里就不會向下流動了,終止操作符只能使用一次。
● collect 收集操作,將所有數(shù)據(jù)收集起來,這個操作非常重要,官方的提供的Collectors 提供了非常多收集器,可以說Stream 的核心在于Collectors。
● count 統(tǒng)計操作,統(tǒng)計最終的數(shù)據(jù)個數(shù)。
● findFirst、findAny 查找操作,查找第一個、查找任何一個 返回的類型為Optional。
● noneMatch、allMatch、anyMatch 匹配操作,數(shù)據(jù)流中是否存在符合條件的元素 返回值為bool 值。
● min、max 最值操作,需要自定義比較器,返回數(shù)據(jù)流中最大最小的值。
● reduce 規(guī)約操作,將整個數(shù)據(jù)流的值規(guī)約為一個值,count、min、max底層就是使用reduce。
● forEach、forEachOrdered 遍歷操作,這里就是對最終的數(shù)據(jù)進(jìn)行消費了。
● toArray 數(shù)組操作,將數(shù)據(jù)流的元素轉(zhuǎn)換成數(shù)組。
這里只介紹了Stream,并沒有涉及到IntStream、LongStream、DoubleStream,這三個流實現(xiàn)了一些特有的操作符,我將在后續(xù)文章中介紹到。
說了這么多,只介紹這些操作符還遠(yuǎn)遠(yuǎn)不夠;俗話說,實踐出真知。那么,Let‘s go。
流的使用詳解
對 Stream 的使用就是實現(xiàn)一個 filter-map-reduce 過程,產(chǎn)生一個最終結(jié)果,或者導(dǎo)致一個副作用(side effect)
流的構(gòu)造與轉(zhuǎn)換
// 1. Individual values
Stream stream = Stream.of("a", "b", "c");
// 2. Arrays
String [] strArray = new String[] {"a", "b", "c"};
stream = Stream.of(strArray);
stream = Arrays.stream(strArray);
// 3. Collections
List<String> list = Arrays.asList(strArray);
stream = list.stream();
需要注意的是,對于基本數(shù)據(jù)類型,目前有三種對應(yīng)的包裝類Stream
IntStream、LongStream、DoubleStream。當(dāng)然我們也可以用 Stream、Stream >、Stream,但是 boxing 和 unboxing 會很耗時,所以特別為這三種基本數(shù)值型提供了對應(yīng)的 Stream。
數(shù)值流的構(gòu)造:
IntStream.of(newint[]{1, 2, 3}).forEach(System.out::println);
? ? ? ?IntStream.range(1, 3).forEach(System.out::println);
? ? ? ?IntStream.rangeClosed(1, 3).forEach(System.out::println);
補充:
foreach語句是java5的新特征之一,在遍歷數(shù)組、集合方面,foreach為開發(fā)人員提供了極大的方便。
foreach語句是for語句的特殊簡化版本,但是foreach語句并不能完全取代for語句,然而,任何的foreach語句都可以改寫為for語句版本。
foreach的語句格式:for(元素類型t 元素變量x : 遍歷對象obj){ 引用了x的java語句; }
二、foreach語句的局限性 通過上面的例子可以發(fā)現(xiàn),如果要引用數(shù)組或者集合的索引,則foreach語句無法做到,foreach僅僅老老實實地遍歷數(shù)組或者集合一遍。
流轉(zhuǎn)換為其他數(shù)據(jù)結(jié)構(gòu)
// 1. Array
String[] strArray1 = stream.toArray(String[]::new);
// 2. Collection
List<String> list1 = stream.collect(Collectors.toList());
List<String> list2 = stream.collect(Collectors.toCollection(ArrayList::new));
Set set1 = stream.collect(Collectors.toSet());
Stack stack1 = stream.collect(Collectors.toCollection(Stack::new));
// 3. String
String str = stream.collect(Collectors.joining()).toString();
一個 Stream 只可以使用一次,上面的代碼只是示例,為了簡潔而重復(fù)使用了數(shù)次(正常開發(fā)只能使用一次)。
我感覺的是轉(zhuǎn)化為stack流
流的操作
當(dāng)把一個數(shù)據(jù)結(jié)構(gòu)包裝成 Stream 后,就要開始對里面的元素進(jìn)行各類操作了。
常見的操作:Intermediate:map (mapToInt, flatMap 等)、 filter、 distinct、 sorted、 peek、 limit、 skip、 parallel、 sequential、 unordered Terminal:forEach、 forEachOrdered、 toArray、 reduce、 collect、 min、 max、 count、 anyMatch、 allMatch、 noneMatch、 findFirst、 findAny、 iterator Short-circuiting:anyMatch、 allMatch、 noneMatch、 findFirst、 findAny、 limit
集合有兩種方式生成流:
stream() 為集合創(chuàng)建串行流
parallelStream() - 為集合創(chuàng)建并行流

具體用法
流的常用創(chuàng)建方法
1.1 使用Collection下的 stream() 和 parallelStream() 方法
List<String> list = new ArrayList<>();
Stream<String> stream = list.stream(); //獲取一個順序流
Stream<String> parallelStream = list.parallelStream(); //獲取一個并行流1.2 使用Arrays 中的 stream() 方法,將數(shù)組轉(zhuǎn)成流
Integer[] nums = new Integer[10];
Stream<Integer> stream = Arrays.stream(nums);1.3 使用Stream中的靜態(tài)方法:of()、iterate()、generate()
Stream<Integer> stream = Stream.of(1,2,3,4,5,6);
Stream<Integer> stream2 = Stream.iterate(0, (x) -> x + 2).limit(6);
stream2.forEach(System.out::println); // 0 2 4 6 8 10
Stream<Double> stream3 = Stream.generate(Math::random).limit(2);
stream3.forEach(System.out::println);1.4 使用 BufferedReader.lines() 方法,將每行內(nèi)容轉(zhuǎn)成流
BufferedReader reader = new BufferedReader(new FileReader("F:\\test_stream.txt"));
Stream<String> lineStream = reader.lines();
lineStream.forEach(System.out::println);1.5 使用 Pattern.splitAsStream() 方法,將字符串分隔成流
Pattern pattern = Pattern.compile(",");
Stream<String> stringStream = pattern.splitAsStream("a,b,c,d");
stringStream.forEach(System.out::println);
2. 流的中間操作
2.1 篩選與切片
filter:過濾流中的某些元素
limit(n):獲取n個元素
skip(n):跳過n元素,配合limit(n)可實現(xiàn)分頁
distinct:通過流中元素的 hashCode() 和 equals() 去除重復(fù)元素
Stream<Integer> stream = Stream.of(6, 4, 6, 7, 3, 9, 8, 10, 12, 14, 14);
Stream<Integer> newStream = stream.filter(s -> s > 5) //6 6 7 9 8 10 12 14 14
? ? ? ?.distinct() //6 7 9 8 10 12 14
? ? ? ?.skip(2) //9 8 10 12 14
? ? ? ?.limit(2); //9 8
newStream.forEach(System.out::println);
2.2 映射
map:接收一個函數(shù)作為參數(shù),該函數(shù)會被應(yīng)用到每個元素上,并將其映射成一個新的元素。flatMap:接收一個函數(shù)作為參數(shù),將流中的每個值都換成另一個流,然后把所有流連接成一個流。
List<String> list = Arrays.asList("a,b,c", "1,2,3");
//將每個元素轉(zhuǎn)成一個新的且不帶逗號的元素
Stream<String> s1 = list.stream().map(s -> s.replaceAll(",", ""));
s1.forEach(System.out::println); // abc ?123
Stream<String> s3 = list.stream().flatMap(s -> {
? ?//將每個元素轉(zhuǎn)換成一個stream
? ?String[] split = s.split(",");
? ?Stream<String> s2 = Arrays.stream(split);
? ?return s2;
});
s3.forEach(System.out::println); // a b c 1 2 3
2.3 排序 sorted():自然排序,流中元素需實現(xiàn)Comparable接口 sorted(Comparator com):定制排序,自定義Comparator排序器
List<String> list = Arrays.asList("aa", "ff", "dd");
//String 類自身已實現(xiàn)Compareable接口
list.stream().sorted().forEach(System.out::println);// aa dd ff
Student s1 = new Student("aa", 10);
Student s2 = new Student("bb", 20);
Student s3 = new Student("aa", 30);
Student s4 = new Student("dd", 40);
List<Student> studentList = Arrays.asList(s1, s2, s3, s4);
//自定義排序:先按姓名升序,姓名相同則按年齡升序
studentList.stream().sorted(
? ? ? ?(o1, o2) -> {
? ? ? ? ? ?if (o1.getName().equals(o2.getName())) {
? ? ? ? ? ? ? ?return o1.getAge() - o2.getAge();
? ? ? ? ? ?} else {
? ? ? ? ? ? ? ?return o1.getName().compareTo(o2.getName());
? ? ? ? ? ?}
? ? ? ?}
).forEach(System.out::println);
2.4 消費 peek:如同于map,能得到流中的每一個元素。但map接收的是一個Function表達(dá)式,有返回值;而peek接收的是Consumer表達(dá)式,沒有返回值。
Student s1 = new Student("aa", 10);
Student s2 = new Student("bb", 20);
List<Student> studentList = Arrays.asList(s1, s2);
studentList.stream()
? ? ? ?.peek(o -> o.setAge(100))
? ? ? ?.forEach(System.out::println); ?
//結(jié)果:
Student{name='aa', age=100}
Student{name='bb', age=100}
3. 流的終止操作
3.1 匹配、聚合操作
allMatch:接收一個 Predicate 函數(shù),當(dāng)流中每個元素都符合該斷言時才返回true,否則返回false
noneMatch:接收一個 Predicate 函數(shù),當(dāng)流中每個元素都不符合該斷言時才返回true,否則返回false
anyMatch:接收一個 Predicate 函數(shù),只要流中有一個元素滿足該斷言則返回true,否則返回false
findFirst:返回流中第一個元素
findAny:返回流中的任意元素
count:返回流中元素的總個數(shù)
max:返回流中元素最大值
min:返回流中元素最小值
List<Integer> list = Arrays.asList(1, 2, 3, 4, 5);
boolean allMatch = list.stream().allMatch(e -> e > 10); //false
boolean noneMatch = list.stream().noneMatch(e -> e > 10); //true
boolean anyMatch = list.stream().anyMatch(e -> e > 4); ?//true
Integer findFirst = list.stream().findFirst().get(); //1
Integer findAny = list.stream().findAny().get(); //1
long count = list.stream().count(); //5
Integer max = list.stream().max(Integer::compareTo).get(); //5
Integer min = list.stream().min(Integer::compareTo).get(); //1
3.2 規(guī)約操作
```java
Optional<T> reduce(BinaryOperator<T> accumulator):第一次執(zhí)行時,accumulator函數(shù)的第一個參數(shù)為流中的第一個元素,第二個參數(shù)為流中元素的第二個元素;第二次執(zhí)行時,第一個參數(shù)為第一次函數(shù)執(zhí)行的結(jié)果,第二個參數(shù)為流中的第三個元素;依次類推。
T reduce(T identity, BinaryOperator<T> accumulator):流程跟上面一樣,只是第一次執(zhí)行時,accumulator函數(shù)的第一個參數(shù)為identity,而第二個參數(shù)為流中的第一個元素。
<U> U reduce(U identity,BiFunction<U, ? super T, U> accumulator,BinaryOperator<U> combiner):在串行流(stream)中,該方法跟第二個方法一樣,即第三個參數(shù)combiner不會起作用。在并行流(parallelStream)中,我們知道流被fork join出多個線程進(jìn)行執(zhí)行,此時每個線程的執(zhí)行流程就跟第二個方法reduce(identity,accumulator)一樣,而第三個參數(shù)combiner函數(shù),則是將每個線程的執(zhí)行結(jié)果當(dāng)成一個新的流,然后使用第一個方法reduce(accumulator)流程進(jìn)行規(guī)約。
```//經(jīng)過測試,當(dāng)元素個數(shù)小于24時,并行時線程數(shù)等于元素個數(shù),當(dāng)大于等于24時,并行時線程數(shù)為16
List<Integer> list = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24);
Integer v = list.stream().reduce((x1, x2) -> x1 + x2).get();
System.out.println(v); ? // 300
Integer v1 = list.stream().reduce(10, (x1, x2) -> x1 + x2);
System.out.println(v1); ?//310
Integer v2 = list.stream().reduce(0,
? ? ? ?(x1, x2) -> {
? ? ? ? ? ?System.out.println("stream accumulator: x1:" + x1 + " ?x2:" + x2);
? ? ? ? ? ?return x1 - x2;
? ? ? ?},
? ? ? ?(x1, x2) -> {
? ? ? ? ? ?System.out.println("stream combiner: x1:" + x1 + " ?x2:" + x2);
? ? ? ? ? ?return x1 * x2;
? ? ? ?});
System.out.println(v2); // -300
Integer v3 = list.parallelStream().reduce(0,
? ? ? ?(x1, x2) -> {
? ? ? ? ? ?System.out.println("parallelStream accumulator: x1:" + x1 + " ?x2:" + x2);
? ? ? ? ? ?return x1 - x2;
? ? ? ?},
? ? ? ?(x1, x2) -> {
? ? ? ? ? ?System.out.println("parallelStream combiner: x1:" + x1 + " ?x2:" + x2);
? ? ? ? ? ?return x1 * x2;
? ? ? ?});
System.out.println(v3); //197474048
3.3 收集操作
collect:接收一個Collector實例,將流中元素收集成另外一個數(shù)據(jù)結(jié)構(gòu)。
?Collector<T, A, R> 是一個接口,有以下5個抽象方法:
?Supplier<A> supplier():創(chuàng)建一個結(jié)果容器A
?BiConsumer<A, T> accumulator():消費型接口,第一個參數(shù)為容器A,第二個參數(shù)為流中元素T。
?BinaryOperator<A> combiner():函數(shù)接口,該參數(shù)的作用跟上一個方法(reduce)中的combiner參數(shù)一樣,將并行流中各個子進(jìn)程的運行結(jié)果(accumulator函數(shù)操作后的容器A)進(jìn)行合并。
?Function<A, R> finisher():函數(shù)式接口,參數(shù)為:容器A,返回類型為:collect方法最終想要的結(jié)果R。
?Set<Characteristics> characteristics():返回一個不可變的Set集合,用來表明該Collector的特征。有以下三個特征:
?CONCURRENT:表示此收集器支持并發(fā)。(官方文檔還有其他描述,暫時沒去探索,故不作過多翻譯)
?UNORDERED:表示該收集操作不會保留流中元素原有的順序。
?IDENTITY_FINISH:表示finisher參數(shù)只是標(biāo)識而已,可忽略。
?注:如果對以上函數(shù)接口不太理解的話,可參考我另外一篇文章:Java 8 函數(shù)式接口
3.3.1 Collector 工具庫:Collectors
Student s1 = new Student("aa", 10,1);
Student s2 = new Student("bb", 20,2);
Student s3 = new Student("cc", 10,3);
List<Student> list = Arrays.asList(s1, s2, s3);
//裝成list
List<Integer> ageList = list.stream().map(Student::getAge).collect(Collectors.toList()); // [10, 20, 10]
//轉(zhuǎn)成set
Set<Integer> ageSet = list.stream().map(Student::getAge).collect(Collectors.toSet()); // [20, 10]
//轉(zhuǎn)成map,注:key不能相同,否則報錯
Map<String, Integer> studentMap = list.stream().collect(Collectors.toMap(Student::getName, Student::getAge)); // {cc=10, bb=20, aa=10}
//字符串分隔符連接
String joinName = list.stream().map(Student::getName).collect(Collectors.joining(",", "(", ")")); // (aa,bb,cc)
//聚合操作
//1.學(xué)生總數(shù)
Long count = list.stream().collect(Collectors.counting()); // 3
//2.最大年齡 (最小的minBy同理)
Integer maxAge = list.stream().map(Student::getAge).collect(Collectors.maxBy(Integer::compare)).get(); // 20
//3.所有人的年齡
Integer sumAge = list.stream().collect(Collectors.summingInt(Student::getAge)); // 40
//4.平均年齡
Double averageAge = list.stream().collect(Collectors.averagingDouble(Student::getAge)); // 13.333333333333334
// 帶上以上所有方法
DoubleSummaryStatistics statistics = list.stream().collect(Collectors.summarizingDouble(Student::getAge));
System.out.println("count:" + statistics.getCount() + ",max:" + statistics.getMax() + ",sum:" + statistics.getSum() + ",average:" + statistics.getAverage());
//分組
Map<Integer, List<Student>> ageMap = list.stream().collect(Collectors.groupingBy(Student::getAge));
//多重分組,先根據(jù)類型分再根據(jù)年齡分
Map<Integer, Map<Integer, List<Student>>> typeAgeMap = list.stream().collect(Collectors.groupingBy(Student::getType, Collectors.groupingBy(Student::getAge)));
//分區(qū)
//分成兩部分,一部分大于10歲,一部分小于等于10歲
Map<Boolean, List<Student>> partMap = list.stream().collect(Collectors.partitioningBy(v -> v.getAge() > 10));
//規(guī)約
Integer allAge = list.stream().map(Student::getAge).collect(Collectors.reducing(Integer::sum)).get(); //40
3.3.2 Collectors.toList() 解析
//toList 源碼
publicstatic <T> Collector<T, ?, List<T>> toList() {
? ?returnnew CollectorImpl<>((Supplier<List<T>>) ArrayList::new, List::add,
? ? ? ? ? ?(left, right) -> {
? ? ? ? ? ? ? ?left.addAll(right);
? ? ? ? ? ? ? ?return left;
? ? ? ? ? ?}, CH_ID);
}
//為了更好地理解,我們轉(zhuǎn)化一下源碼中的lambda表達(dá)式
public <T> Collector<T, ?, List<T>> toList() {
? ?Supplier<List<T>> supplier = () -> new ArrayList();
? ?BiConsumer<List<T>, T> accumulator = (list, t) -> list.add(t);
? ?BinaryOperator<List<T>> combiner = (list1, list2) -> {
? ? ? ?list1.addAll(list2);
? ? ? ?return list1;
? ?};
? ?Function<List<T>, List<T>> finisher = (list) -> list;
? ?Set<Collector.Characteristics> characteristics = Collections.unmodifiableSet(EnumSet.of(Collector.Characteristics.IDENTITY_FINISH));
? ?returnnew Collector<T, List<T>, List<T>>() {
? ? ? ?@Override
? ? ? ?public Supplier supplier() {
? ? ? ? ? ?return supplier;
? ? ? ?}
? ? ? ?@Override
? ? ? ?public BiConsumer accumulator() {
? ? ? ? ? ?return accumulator;
? ? ? ?}
? ? ? ?@Override
? ? ? ?public BinaryOperator combiner() {
? ? ? ? ? ?return combiner;
? ? ? ?}
? ? ? ?@Override
? ? ? ?public Function finisher() {
? ? ? ? ? ?return finisher;
? ? ? ?}
? ? ? ?@Override
? ? ? ?public Set<Characteristics> characteristics() {
? ? ? ? ? ?return characteristics;
? ? ? ?}
? ?};
}
具體用法:
map/flatMap
//轉(zhuǎn)換大寫,wordList為單詞集合List<String>類型
List<String> output = wordList.stream().map(String::toUpperCase).collect(Collectors.toList());
求平方數(shù)
//這段代碼生成一個整數(shù) list 的平方數(shù) {1, 4, 9, 16}。
List<Integer> nums = Arrays.asList(1, 2, 3, 4);
List<Integer> squareNums = nums.stream().map(n -> n * n).collect(Collectors.toList());
從上面的例子可以看出來,map 生成的是個 1:1 映射,每個輸入元素,都按照規(guī)則轉(zhuǎn)換成為另外一個元素(一對一映射)
一對多映射
//將最底層元素抽出來放到一起,最終 output 的新 Stream 里面已經(jīng)沒有 List 了,都是直接的數(shù)字。
Stream<List<Integer>> inputStream = Stream.of(
Arrays.asList(1),
Arrays.asList(2, 3),
Arrays.asList(4, 5, 6)
);
Stream<Integer> outputStream = inputStream.flatMap((childList) -> childList.stream());
List<Integer> list =outputStream.collect(Collectors.toList());
System.out.println(list.toString());
filter
filter 對原始 Stream 進(jìn)行某項測試,通過測試的元素被留下來生成一個新 Stream。
//留下偶數(shù),經(jīng)過條件“被 2 整除”的 filter,剩下的數(shù)字為 {2, 4, 6}。
Integer[] sixNums = {1, 2, 3, 4, 5, 6};
Integer[] evens =Stream.of(sixNums).filter(n -> n%2 == 0).toArray(Integer[]::new);
//把每行的單詞用 flatMap 整理到新的 Stream,然后保留長度不為 0 的,就是整篇文章中的全部單詞了。
//REGEXP為正則表達(dá)式,具體邏輯具體分析
List<String> output = reader.lines().
? ?flatMap(line -> Stream.of(line.split(REGEXP))).
? ?filter(word -> word.length() > 0).collect(Collectors.toList());
forEach
forEach 方法接收一個 Lambda 表達(dá)式,然后在 Stream 的每一個元素上執(zhí)行該表達(dá)式
//打印所有男性姓名,roster為person集合類型為List<Pserson>
// Java 8
roster.stream().filter(p -> p.getGender() == Person.Sex.MALE).forEach(p -> System.out.println(p.getName()));
// Pre-Java 8
for (Person p : roster) {
? ?if (p.getGender() == Person.Sex.MALE) {
? ? ? ?System.out.println(p.getName());
? ?}
}
當(dāng)需要為多核系統(tǒng)優(yōu)化時,可以 parallelStream().forEach(),只是此時原有元素的次序沒法保證,并行的情況下將改變串行時操作的行為,此時 forEach 本身的實現(xiàn)不需要調(diào)整,而 Java8 以前的 for 循環(huán) code 可能需要加入額外的多線程邏輯。
另外一點需要注意,forEach 是 terminal 操作,因此它執(zhí)行后,Stream 的元素就被“消費”掉了,你無法對一個 Stream 進(jìn)行兩次 terminal 運算
注意:一個stream不可以使用兩次
具有相似功能的 intermediate 操作 peek 可以達(dá)到上述目的。
peek 對每個元素執(zhí)行操作并返回一個新的 StreamStream.of("one","two","three","four").
? ?filter(e ->e.length()>3).
? ?peek(e ->System.out.println("Filtered value: "+e)).
? ?map(String::toUpperCase) ? ? .
? ?peek(e ->System.out.println("Mapped value: "+e)).
? ?collect(Collectors.toList());
findFirst 這是一個 termimal 兼 short-circuiting 操作,它總是返回 Stream 的第一個元素,或者空。
這里比較重點的是它的返回值類型:Optional。這也是一個模仿 Scala 語言中的概念,作為一個容器,它可能含有某值,或者不包含。使用它的目的是盡可能避免 NullPointerException。
?String strA = " abcd ", strB = null;
? ?print(strA);
? ?print("");
? ?print(strB);
? ?getLength(strA);
? ?getLength("");
? ?getLength(strB);
? ?//輸出text不為null的值public static void print(String text)
? ?{
? ? ? ?Java 8 Optional.ofNullable(text).ifPresent(System.out::println);
? ? ? ?Pre - Java 8if (text != null) {
? ? ? ?System.out.println(text);
? ?}
? ?}
? ?//輸出text的長度,避免空指針
? ?public static int getLength(String text) {
? ? ? ?Java 8
? ? ? ?return Optional.ofNullable(text).map(String::length).orElse(-1);
? ? ? ?Pre - Java 8
? ? ? ?returnif (text != null) ?text.length() :-1;
? ?}在更復(fù)雜的 if (xx != null) 的情況中,使用 Optional 代碼的可讀性更好,而且它提供的是編譯時檢查,能極大的降低 NPE 這種 Runtime Exception 對程序的影響,或者迫使程序員更早的在編碼階段處理空值問題,而不是留到運行時再發(fā)現(xiàn)和調(diào)試。
Stream 中的 findAny、max/min、reduce 等方法等返回 Optional 值。還有例如 IntStream.average() 返回 OptionalDouble 等等
reduce
這個方法的主要作用是把 Stream 元素組合起來。
它提供一個起始值(種子),然后依照運算規(guī)則(BinaryOperator),和前面 Stream 的第一個、第二個、第 n 個元素組合。從這個意義上說,字符串拼接、數(shù)值的 sum、min、max、average 都是特殊的 reduce。也有沒有起始值的情況,這時會把 Stream 的前面兩個元素組合起來,返回的是 Optional。
? ? reduce用例
? ? 字符串連接,concat = "ABCD"String concat = Stream.of("A", "B", "C", "D").reduce("", String::concat);
? ? 求最小值,minValue = -3.0double minValue = Stream.of(-1.5, 1.0, -3.0, -2.0).reduce(Double.MAX_VALUE, Double::min);
? ? 求和,sumValue = 10, 有起始值int sumValue = Stream.of(1, 2, 3, 4).reduce(0, Integer::sum);
? ? 求和,sumValue = 10, 無起始值,返回Optional,所以有g(shù)et()方法sumValue = Stream.of(1, 2, 3, 4).reduce(Integer::sum).get();
? ? 過濾,字符串連接,concat = "ace"concat = Stream.of("a", "B", "c", "D", "e", "F") ? ?.filter(x -> x.compareTo("Z") > 0) ? ?.reduce("", String::concat);limit/skip
limit 返回 Stream 的前面 n 個元素;skip 則是扔掉前 n 個元素(它是由一個叫 subStream 的方法改名而來)。
//limit 和 skip 對運行次數(shù)的影響public
? ?void testLimitAndSkip() { ? ? List<Person> persons = new ArrayList();
? ? ? ?for (int i = 1; i <= 10000; i++) { ?
? ? ? ? ? ?Person person = new Person(i, "name" + i); ? ? ?
? ? ? ? ? ?persons.add(person); ? ?
? ? ? ?} ? ?
? ? ? ?List<String> personList2 = persons.stream()
? ? ? ? ? ? ? ?.map(Person::getName).limit(10).skip(3) ? ?
? ? ? ? ? ? ? ?.collect(Collectors.toList()); ? ? ?
? ? ? ?System.out.println(personList2);
? ?}
? ?privateclass Person { ? ?
? ? ? ?publicint no; ?
? ? ? ?private String name; ?
? ? ? ?public Person (int no, String name) { ? ? ?
? ? ? ? ? ?this.no = no; ? ? ?
? ? ? ? ? ?this.name = name; ?
? ? ? ?} ? ?
? ? ? ?public String getName() { ?
? ? ? ? ? ?System.out.println(name);
? ? ? ? ? ?return name; ? ?
? ? ? ?}
? ?}
? ?//結(jié)果name1name2name3name4name5name6name7name8name9name10[name4, name5, name6, name7, name8, name9, name10]
? ?// 這是一個有 10,000 個元素的 Stream,但在 short-circuiting 操作 limit 和 skip 的作用下,
? ?// 管道中 map 操作指定的 getName() 方法的執(zhí)行次數(shù)為 limit 所限定的 10 次,而最終返回結(jié)果在跳過前 3 個元素后只有后面 7 個返回。有一種情況是 limit/skip 無法達(dá)到 short-circuiting 目的的,就是把它們放在 Stream 的排序操作后,原因跟 sorted 這個 intermediate 操作有關(guān):此時系統(tǒng)并不知道 Stream 排序后的次序如何,所以 sorted 中的操作看上去就像完全沒有被 limit 或者 skip 一樣。
?List<Person> persons = new ArrayList();
? ?for (int i = 1; i <= 5; i++) { ? ?
? ? ? ?Person person = new Person(i, "name" + i); ?
? ? ? ?persons.add(person);
? ?}
? ?List<Person> personList2 = persons.stream().sorted((p1, p2) -> p1.getName().compareTo(p2.getName())).limit(2).collect(Collectors.toList());
? ?System.out.println(personList2);
? ?//結(jié)果name2name1name3name2name4name3name5name4[stream.StreamDW$Person@816f27d, stream.StreamDW$Person@87aac27]//雖然最后的返回元素數(shù)量是
? ?// 2,但整個管道中的 sorted 表達(dá)式執(zhí)行次數(shù)沒有像前面例子相應(yīng)減少。sorted
對 Stream 的排序通過 sorted 進(jìn)行,它比數(shù)組的排序更強之處在于你可以首先對 Stream 進(jìn)行各類 map、filter、limit、skip 甚至 distinct 來減少元素數(shù)量后,再排序,這能幫助程序明顯縮短執(zhí)行時間。
?List<Person> persons = new ArrayList();
? ?for (int i = 1; i <= 5; i++) { ?
? ? ? ?Person person = new Person(i, "name" + i); ?
? ? ? ?persons.add(person);
? ?}
? ?List<Person> personList2 = persons.stream().limit(2)
? ? ? ? ? ?.sorted((p1, p2) -> p1.getName().compareTo(p2.getName())).collect(Collectors.toList());
? ?System.out.println(personList2);
? ?//結(jié)果name2name1[stream.StreamDW$Person@6ce253f1, stream.StreamDW$Person@53d8d10a]
min/max/distinct
min 和 max 的功能也可以通過對 Stream 元素先排序,再 findFirst 來實現(xiàn),但前者的性能會更好,為 O(n),而 sorted 的成本是 O(n log n)。同時它們作為特殊的 reduce 方法被獨立出來也是因為求最大最小值是很常見的操作。
//找出最長一行的長度
? ?BufferedReader br = new BufferedReader(new FileReader("c:\\Service.log"));
? ?int longest = br.lines().mapToInt(String::length).max().getAsInt();
? ?br.close();
? ?System.out.println(longest);
? ?//找出全文的單詞,轉(zhuǎn)小寫,并排序,使用 distinct 來找出不重復(fù)的單詞。單詞間只有空格
? ?List<String> words = br.lines()
? ?.flatMap(line -> Stream.of(line.split(" "))) ? ?.filter(word -> word.length() > 0) ?
? ? .map(String::toLowerCase) ? ?.distinct().sorted() ? ?.collect(Collectors.toList());
? ?br.close();
? ?System.out.println(words);
Match
Stream 有三個 match 方法,從語義上說:
allMatch:Stream 中全部元素符合傳入的 predicate,返回 true
anyMatch:Stream 中只要有一個元素符合傳入的 predicate,返回 true
noneMatch:Stream 中沒有一個元素符合傳入的 predicate,返回 true
它們都不是要遍歷全部元素才能返回結(jié)果。例如 allMatch 只要一個元素不滿足條件,就 skip 剩下的所有元素,返回 false。
自己生成流
通過實現(xiàn) Supplier 接口,你可以自己來控制流的生成。這種情形通常用于隨機數(shù)、常量的 Stream,或者需要前后元素間維持著某種狀態(tài)信息的 Stream。把 Supplier 實例傳遞給 Stream.generate() 生成的 Stream,默認(rèn)是串行(相對 parallel 而言)但無序的(相對 ordered 而言)。由于它是無限的,在管道中,必須利用 limit 之類的操作限制 Stream 大小。
//生成10個隨機數(shù)
? ?Random seed = new Random();
? ?Supplier<Integer> random = seed::nextInt;Stream.generate(random).limit(10).forEach(System.out::println);
? ?Another wayIntStream.generate(() -> (int) (System.nanoTime() % 100)).limit(10).forEach(System.out::println);Stream.generate() 還接受自己實現(xiàn)的 Supplier。例如在構(gòu)造海量測試數(shù)據(jù)的時候,用某種自動的規(guī)則給每一個變量賦值;或者依據(jù)公式計算 Stream 的每個元素值。這些都是維持狀態(tài)信息的情形。
?Stream.generate(new PersonSupplier()).
? ?limit(10).forEach(p -> System.out.println(p.getName() + ", " + p.getAge()));
? privateclass PersonSupplier implements Supplier<Person> { ? ?
? ? ? ?privateint index = 0; ?
? ? ? ?private Random random = new Random(); ?
? ? ? ?@Override
? ? ? ?public Person get() { ? ? ?
? ? ? ? ? ?returnnew Person(index++, "StormTestUser" + index, random.nextInt(100));
? ? ? ?}
? }
? //結(jié)果StormTestUser1, 9StormTestUser2, 12StormTestUser3, 88StormTestUser4,
? ?// 51StormTestUser5, 22StormTestUser6, 28StormTestUser7, 81StormTestUser8,
? ?// 51StormTestUser9, 4StormTestUser10, 76Stream.iterate
iterate 跟 reduce 操作很像,接受一個種子值,和一個 UnaryOperator(例如 f)。然后種子值成為 Stream 的第一個元素,f(seed) 為第二個,f(f(seed)) 第三個,以此類推
//生成等差數(shù)列
? ?Stream.iterate(0, n -> n + 3).limit(10). forEach(x -> System.out.print(x + " "));
? ?//結(jié)果0 3 6 9 12 15 18 21 24 27Stream.generate 相仿,在 iterate 時候管道必須有 limit 這樣的操作來限制 Stream 大小。
用 Collectors 來進(jìn)行 reduction 操作
java.util.stream.Collectors 類的主要作用就是輔助進(jìn)行各類有用的 reduction 操作,例如轉(zhuǎn)變輸出為 Collection,把 Stream 元素進(jìn)行歸組。
groupingBy/partitioningBy
//按照年齡歸組
? ?Map<Integer, List<Person>> personGroups = Stream.generate(new PersonSupplier()) ?
? ? ? ? ? ?.limit(100).collect(Collectors.groupingBy(Person::getAge));
? ?Iterator it = personGroups.entrySet().iterator();while (it.hasNext()) { ?
? ? ? ?Map.Entry<Integer, List<Person>> persons = (Map.Entry) it.next(); ? ?
? ? ? ?System.out.println("Age " + persons.getKey() + " = " + persons.getValue().size());
? ?}
? ?//上面的 code,首先生成 100 人的信息,然后按照年齡歸組,相同年齡的人放到同一個 list 中,
? ?// 如下的輸出:Age 0 = 2Age 1 = 2Age 5 = 2Age 8 = 1Age 9 = 1Age 11 = 2……Map<Boolean, List<Person>> children = Stream.generate(new PersonSupplier()) ?
? ? ? ? ? ?.limit(100).collect(Collectors.partitioningBy(p -> p.getAge() < 18));
? ?System.out.println("Children number: " + children.get(true).size());
? ?System.out.println("Adult number: " + children.get(false).size());
? ?//結(jié)果Children number: 23 Adult number: 77Stream 的特性可以歸納為:
不是數(shù)據(jù)結(jié)構(gòu)它沒有內(nèi)部存儲,它只是用操作管道從 source(數(shù)據(jù)結(jié)構(gòu)、數(shù)組、generator function、IO channel)抓取數(shù)據(jù)。它也絕不修改自己所封裝的底層數(shù)據(jù)結(jié)構(gòu)的數(shù)據(jù)。例如 Stream 的 filter 操作會產(chǎn)生一個不包含被過濾元素的新 Stream,而不是從 source 刪除那些元素。所有 Stream 的操作必須以 lambda 表達(dá)式為參數(shù)不支持索引訪問你可以請求第一個元素,但無法請求第二個,第三個,或最后一個。不過請參閱下一項。很容易生成數(shù)組或者 List惰性化很多 Stream 操作是向后延遲的,一直到它弄清楚了最后需要多少數(shù)據(jù)才會開始。Intermediate 操作永遠(yuǎn)是惰性化的。并行能力當(dāng)一個 Stream 是并行化的,就不需要再寫多線程代碼,所有對它的操作會自動并行進(jìn)行的??梢允菬o限的集合有固定大小,Stream 則不必。limit(n) 和 findFirst() 這類的 short-circuiting 操作可以對無限的 Stream 進(jìn)行運算并很快完成。
當(dāng)把一個數(shù)據(jù)結(jié)構(gòu)包裝成 Stream 后,就要開始對里面的元素進(jìn)行各類操作了,
Java 8 API添加了一個新的抽象稱為流Stream,可以讓你以一種聲明的方式處理數(shù)據(jù)。
?//Match使用示例
? ?List<Person> persons = new ArrayList();
? ?persons.add(new Person(1, "name" + 1, 10));
? ?persons.add(new Person(2, "name" + 2, 21));
? ?persons.add(new Person(3, "name" + 3, 34));
? ?persons.add(new Person(4, "name" + 4, 6));
? ?persons.add(new Person(5, "name" + 5, 55));
? ?boolean isAllAdult = persons.stream().allMatch(p -> p.getAge() > 18);
? ?System.out.println("All are adult? " + isAllAdult);
? ?boolean isThereAnyChild = persons.stream().anyMatch(p -> p.getAge() < 12);
? ?System.out.println("Any child? " + isThereAnyChild);
? ?//結(jié)果All are adult? falseAny child? true

