Java 8 Stream Study

Java 8 Stream Study

Java 8 Stream

Java 8 API添加了一個(gè)新的抽象稱為流Stream宗侦,可以讓你以一種聲明的方式處理數(shù)據(jù)作郭。

Stream 使用一種類似用 SQL 語句從數(shù)據(jù)庫查詢數(shù)據(jù)的直觀方式來提供一種對(duì) Java 集合運(yùn)算和表達(dá)的高階抽象。

Stream API可以極大提高Java程序員的生產(chǎn)力正塌,讓程序員寫出高效率耀里、干凈、簡(jiǎn)潔的代碼缩滨。

這種風(fēng)格將要處理的元素集合看作一種流, 流在管道中傳輸, 并且可以在管道的節(jié)點(diǎn)上進(jìn)行處理脉漏, 比如篩選蛋勺, 排序,聚合等鸠删。

元素流在管道中經(jīng)過中間操作(intermediate operation)的處理,最后由最終操作(terminal operation)得到前面處理的結(jié)果贼陶。

+--------------------+       +------+   +------+   +---+   +-------+
| stream of elements +-----> |filter+-> |sorted+-> |map+-> |collect|
+--------------------+       +------+   +------+   +---+   +-------+
  • my筆記
+--------------------+       +------+   +------+   +---+   
| stream of elements +-----> |filter+-> |sorted+-> |map+-> ......
+--------------------+       +------+   +------+   +---+   
                             +------+   +------+   +---+   +-------+
                          -> |filter+-> |sorted+-> |map+-> |collect|
                             +------+   +------+   +---+   +-------+

以上的流程轉(zhuǎn)換為 Java 代碼為:

List<Integer> transactionsIds = 
widgets.stream()
             .filter(b -> b.getColor() == RED)
             .sorted((x,y) -> x.getWeight() - y.getWeight())
             .mapToInt(Widget::getWeight)
             .sum();

什么是 Stream刃泡?

Stream(流)是一個(gè)來自數(shù)據(jù)源的元素隊(duì)列并支持聚合操作

  • 元素是特定類型的對(duì)象,形成一個(gè)隊(duì)列碉怔。 Java中的Stream并不會(huì)存儲(chǔ)元素烘贴,而是按需計(jì)算。
  • 數(shù)據(jù)源 流的來源撮胧。 可以是集合桨踪,數(shù)組,I/O channel芹啥, 產(chǎn)生器generator 等锻离。
  • 聚合操作 類似SQL語句一樣的操作, 比如filter, map, reduce, find, match, sorted等墓怀。

和以前的Collection操作不同汽纠, Stream操作還有兩個(gè)基礎(chǔ)的特征:

  • Pipelining: 中間操作都會(huì)返回流對(duì)象本身。 這樣多個(gè)操作可以串聯(lián)成一個(gè)管道傀履, 如同流式風(fēng)格(fluent style)虱朵。 這樣做可以對(duì)操作進(jìn)行優(yōu)化, 比如延遲執(zhí)行(laziness)和短路( short-circuiting)钓账。
  • 內(nèi)部迭代: 以前對(duì)集合遍歷都是通過Iterator或者For-Each的方式, 顯式的在集合外部進(jìn)行迭代碴犬, 這叫做外部迭代。 Stream提供了內(nèi)部迭代的方式梆暮, 通過訪問者模式(Visitor)實(shí)現(xiàn)服协。

生成流

在 Java 8 中, 集合接口有兩個(gè)方法來生成流:

  • stream() ? 為集合創(chuàng)建串行流。
  • parallelStream() ? 為集合創(chuàng)建并行流啦粹。
List<String> strings = Arrays.asList("abc", "", "bc", "efg", "abcd","", "jkl");
List<String> filtered = strings.stream().filter(string -> !string.isEmpty()).collect(Collectors.toList());

forEach

Stream 提供了新的方法 'forEach' 來迭代流中的每個(gè)數(shù)據(jù)蚯涮。以下代碼片段使用 forEach 輸出了10個(gè)隨機(jī)數(shù):

Random random = new Random(); 
random.ints().limit(10).forEach(System.out::println);

map

map 方法用于映射每個(gè)元素到對(duì)應(yīng)的結(jié)果,以下代碼片段使用 map 輸出了元素對(duì)應(yīng)的平方數(shù):

List<Integer> numbers = Arrays.asList(3, 2, 2, 3, 7, 3, 5); 
// 獲取對(duì)應(yīng)的平方數(shù) 
List<Integer> squaresList = numbers.stream().map( i -> i*i).distinct().collect(Collectors.toList());

filter

filter 方法用于通過設(shè)置的條件過濾出元素卖陵。以下代碼片段使用 filter 方法過濾出空字符串:

List<String>strings = Arrays.asList("abc", "", "bc", "efg", "abcd","", "jkl"); 
// 獲取空字符串的數(shù)量 
long count = strings.stream().filter(string -> string.isEmpty()).count();

limit

limit 方法用于獲取指定數(shù)量的流遭顶。 以下代碼片段使用 limit 方法打印出 10 條數(shù)據(jù):

Random random = new Random(); 
random.ints().limit(10).forEach(System.out::println);

sorted

sorted 方法用于對(duì)流進(jìn)行排序。以下代碼片段使用 sorted 方法對(duì)輸出的 10 個(gè)隨機(jī)數(shù)進(jìn)行排序:

Random random = new Random();
random.ints().limit(10).sorted().forEach(System.out::println);

并行(parallel)程序

parallelStream 是流并行處理程序的代替方法泪蔫。以下實(shí)例我們使用 parallelStream 來輸出空字符串的數(shù)量:

List<String> strings = Arrays.asList("abc", "", "bc", "efg", "abcd","", "jkl"); 
// 獲取空字符串的數(shù)量 
long count = strings.parallelStream().filter(string -> string.isEmpty()).count();

我們可以很容易的在順序運(yùn)行和并行直接切換棒旗。


Collectors

Collectors 類實(shí)現(xiàn)了很多歸約操作,例如將流轉(zhuǎn)換成集合和聚合元素。Collectors 可用于返回列表或字符串:

List<String>strings = Arrays.asList("abc", "", "bc", "efg", "abcd","", "jkl"); 
List<String> filtered = strings.stream().filter(string -> !string.isEmpty()).collect(Collectors.toList());  
System.out.println("篩選列表: " + filtered); 
String mergedString = strings.stream().filter(string -> !string.isEmpty()).collect(Collectors.joining(", ")); 
System.out.println("合并字符串: " + mergedString);

統(tǒng)計(jì)

另外铣揉,一些產(chǎn)生統(tǒng)計(jì)結(jié)果的收集器也非常有用饶深。它們主要用于int、double逛拱、long等基本類型上敌厘,它們可以用來產(chǎn)生類似如下的統(tǒng)計(jì)結(jié)果。

List<Integer> numbers = Arrays.asList(3, 2, 2, 3, 7, 3, 5);  
IntSummaryStatistics stats = numbers.stream().mapToInt((x) -> x).summaryStatistics();  
System.out.println("列表中最大的數(shù) : " + stats.getMax()); 
System.out.println("列表中最小的數(shù) : " + stats.getMin()); 
System.out.println("所有數(shù)之和 : " + stats.getSum()); 
System.out.println("平均數(shù) : " + stats.getAverage());

stream的特點(diǎn)

特點(diǎn):

  1. 不是數(shù)據(jù)結(jié)構(gòu)朽合,不會(huì)保存數(shù)據(jù)俱两。
  2. 不會(huì)修改原來的數(shù)據(jù)源,它會(huì)將操作后的數(shù)據(jù)保存到另外一個(gè)對(duì)象中曹步。
  3. 惰性求值宪彩,流在中間處理過程中,只是對(duì)操作進(jìn)行了記錄讲婚,并不會(huì)立即執(zhí)行尿孔,需要等到執(zhí)行終止操作的時(shí)候才會(huì)進(jìn)行實(shí)際的計(jì)算。

Stream操作分類

Stream操作分類

無狀態(tài):指元素的處理不受之前元素的影響筹麸;

有狀態(tài):指該操作只有拿到所有元素之后才能繼續(xù)下去活合。

非短路操作:指必須處理所有元素才能得到最終結(jié)果;

短路操作:指遇到某些符合條件的元素就可以得到最終結(jié)果物赶,如 A || B芜辕,只要A為true,則無需判斷B的結(jié)果块差。

常用中間件

filter:過濾流侵续,過濾流中的元素,返回一個(gè)符合條件的Stream

map:轉(zhuǎn)換流憨闰,將一種類型的流轉(zhuǎn)換為另外一種流状蜗。(mapToInt、mapToLong鹉动、mapToDouble 返回int轧坎、long、double基本類型對(duì)應(yīng)的Stream)

flatMap:簡(jiǎn)單的說泽示,就是一個(gè)或多個(gè)流合并成一個(gè)新流缸血。(flatMapToInt、flatMapToLong械筛、flatMapToDouble 返回對(duì)應(yīng)的IntStream捎泻、LongStream、DoubleStream流埋哟。)

distinct:返回去重的Stream笆豁。

sorted:返回一個(gè)排序的Stream。

peek:主要用來查看流中元素的數(shù)據(jù)狀態(tài)。

limit:返回前n個(gè)元素?cái)?shù)據(jù)組成的Stream闯狱。屬于短路操作

skip:返回第n個(gè)元素后面數(shù)據(jù)組成的Stream煞赢。

結(jié)束操作

forEach: 循環(huán)操作Stream中數(shù)據(jù)。

toArray: 返回流中元素對(duì)應(yīng)的數(shù)組對(duì)象哄孤。

reduce: 聚合操作照筑,用來做統(tǒng)計(jì)。

collect: 聚合操作瘦陈,封裝目標(biāo)數(shù)據(jù)凝危。

min、max双饥、count: 聚合操作,最小值弟断,最大值咏花,總數(shù)量。

anyMatch: 短路操作阀趴,有一個(gè)符合條件返回true昏翰。

allMatch: 所有數(shù)據(jù)都符合條件返回true。

noneMatch: 所有數(shù)據(jù)都不符合條件返回true刘急。

findFirst: 短路操作棚菊,獲取第一個(gè)元素。

findAny: 短路操作叔汁,獲取任一元素统求。

forEachOrdered: 暗元素順序執(zhí)行循環(huán)操作。

具體用法

1. 流的常用創(chuàng)建方法

1.1 使用Collection下的 stream() 和 parallelStream() 方法

List<String> list = new ArrayList<>();
Stream<String> stream = list.stream(); //獲取一個(gè)順序流
Stream<String> parallelStream = list.parallelStream(); //獲取一個(gè)并行流

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("d:\\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個(gè)元素
skip(n):跳過n元素,配合limit(n)可實(shí)現(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:接收一個(gè)函數(shù)作為參數(shù)边篮,該函數(shù)會(huì)被應(yīng)用到每個(gè)元素上己莺,并將其映射成一個(gè)新的元素。
flatMap:接收一個(gè)函數(shù)作為參數(shù)戈轿,將流中的每個(gè)值都換成另一個(gè)流凌受,然后把所有流連接成一個(gè)流。

List<String> list = Arrays.asList("a,b,c", "1,2,3");

//將每個(gè)元素轉(zhuǎn)成一個(gè)新的且不帶逗號(hào)的元素
Stream<String> s1 = list.stream().map(s -> s.replaceAll(",", ""));
s1.forEach(System.out::println); // abc  123
Stream<String> s3 = list.stream().flatMap(s -> {
    //將每個(gè)元素轉(zhuǎn)換成一個(gè)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():自然排序思杯,流中元素需實(shí)現(xiàn)Comparable接口
sorted(Comparator com):定制排序胁艰,自定義Comparator排序器

List<String> list = Arrays.asList("aa", "ff", "dd");

//String 類自身已實(shí)現(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 消費(fèi)
peek:如同于map,能得到流中的每一個(gè)元素腾么。但map接收的是一個(gè)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:接收一個(gè) Predicate 函數(shù)殴泰,當(dāng)流中每個(gè)元素都符合該斷言時(shí)才返回true于宙,否則返回false
noneMatch:接收一個(gè) Predicate 函數(shù),當(dāng)流中每個(gè)元素都不符合該斷言時(shí)才返回true悍汛,否則返回false
anyMatch:接收一個(gè) Predicate 函數(shù)捞魁,只要流中有一個(gè)元素滿足該斷言則返回true,否則返回false
findFirst:返回流中第一個(gè)元素
findAny:返回流中的任意元素
count:返回流中元素的總個(gè)數(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ī)約操作
Optional<T> reduce(BinaryOperator<T> accumulator):第一次執(zhí)行時(shí)离咐,accumulator函數(shù)的第一個(gè)參數(shù)為流中的第一個(gè)元素谱俭,第二個(gè)參數(shù)為流中元素的第二個(gè)元素;第二次執(zhí)行時(shí)宵蛀,第一個(gè)參數(shù)為第一次函數(shù)執(zhí)行的結(jié)果昆著,第二個(gè)參數(shù)為流中的第三個(gè)元素;依次類推术陶。
T reduce(T identity, BinaryOperator<T> accumulator):流程跟上面一樣凑懂,只是第一次執(zhí)行時(shí),accumulator函數(shù)的第一個(gè)參數(shù)為identity梧宫,而第二個(gè)參數(shù)為流中的第一個(gè)元素接谨。
<U> U reduce(U identity,BiFunction<U, ? super T, U> accumulator,BinaryOperator<U> combiner):在串行流(stream)中,該方法跟第二個(gè)方法一樣塘匣,即第三個(gè)參數(shù)combiner不會(huì)起作用疤坝。在并行流(parallelStream)中,我們知道流被fork join出多個(gè)線程進(jìn)行執(zhí)行,此時(shí)每個(gè)線程的執(zhí)行流程就跟第二個(gè)方法reduce(identity,accumulator)一樣馆铁,而第三個(gè)參數(shù)combiner函數(shù)跑揉,則是將每個(gè)線程的執(zhí)行結(jié)果當(dāng)成一個(gè)新的流,然后使用第一個(gè)方法reduce(accumulator)流程進(jìn)行規(guī)約埠巨。

//經(jīng)過測(cè)試历谍,當(dāng)元素個(gè)數(shù)小于24時(shí),并行時(shí)線程數(shù)等于元素個(gè)數(shù)辣垒,當(dāng)大于等于24時(shí)望侈,并行時(shí)線程數(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:接收一個(gè)Collector實(shí)例,將流中元素收集成另外一個(gè)數(shù)據(jù)結(jié)構(gòu)勋桶。
Collector<T, A, R> 是一個(gè)接口脱衙,有以下5個(gè)抽象方法:
Supplier<A> supplier():創(chuàng)建一個(gè)結(jié)果容器A
BiConsumer<A, T> accumulator():消費(fèi)型接口侥猬,第一個(gè)參數(shù)為容器A,第二個(gè)參數(shù)為流中元素T捐韩。
BinaryOperator<A> combiner():函數(shù)接口退唠,該參數(shù)的作用跟上一個(gè)方法(reduce)中的combiner參數(shù)一樣,將并行流中各 個(gè)子進(jìn)程的運(yùn)行結(jié)果(accumulator函數(shù)操作后的容器A)進(jìn)行合并荤胁。
Function<A, R> finisher():函數(shù)式接口瞧预,參數(shù)為:容器A,返回類型為:collect方法最終想要的結(jié)果R仅政。
Set<Characteristics> characteristics():返回一個(gè)不可變的Set集合垢油,用來表明該Collector的特征。有以下三個(gè)特征:
CONCURRENT:表示此收集器支持并發(fā)圆丹。(官方文檔還有其他描述滩愁,暫時(shí)沒去探索,故不作過多翻譯)
UNORDERED:表示該收集操作不會(huì)保留流中元素原有的順序辫封。
IDENTITY_FINISH:表示finisher參數(shù)只是標(biāo)識(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不能相同秸讹,否則報(bào)錯(cuò)
Map<String, Integer> studentMap = list.stream().collect(Collectors.toMap(Student::getName, Student::getAge)); // {cc=10, bb=20, aa=10}
//這里要注意檀咙,如果你list轉(zhuǎn)map的key如果不唯一雅倒,會(huì)報(bào)錯(cuò)璃诀,所以如果你不確定你的key是否唯一,可以改成如下:
Map<Integer, String> map = persionList.stream().collect(
                Collectors.toMap(Person::getAge, Person::getName, (key1, key2) -> key1)
        );
//字符串分隔符連接
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 源碼
public static <T> Collector<T, ?, List<T>> toList() {
    return new 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));

    return new 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;
        }
    };
}

參考鏈接:

  1. Java 8 Stream
  2. java代碼之美(2)
  3. Java 8 stream的詳細(xì)用法
  4. 深入理解Java Stream流水線
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末裁良,一起剝皮案震驚了整個(gè)濱河市凿将,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌价脾,老刑警劉巖牧抵,帶你破解...
    沈念sama閱讀 218,122評(píng)論 6 505
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異侨把,居然都是意外死亡犀变,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,070評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門秋柄,熙熙樓的掌柜王于貴愁眉苦臉地迎上來获枝,“玉大人,你說我怎么就攤上這事骇笔∈〉辏” “怎么了嚣崭?”我有些...
    開封第一講書人閱讀 164,491評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵,是天一觀的道長懦傍。 經(jīng)常有香客問我雹舀,道長,這世上最難降的妖魔是什么谎脯? 我笑而不...
    開封第一講書人閱讀 58,636評(píng)論 1 293
  • 正文 為了忘掉前任葱跋,我火速辦了婚禮,結(jié)果婚禮上源梭,老公的妹妹穿的比我還像新娘娱俺。我一直安慰自己,他們只是感情好废麻,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,676評(píng)論 6 392
  • 文/花漫 我一把揭開白布荠卷。 她就那樣靜靜地躺著,像睡著了一般烛愧。 火紅的嫁衣襯著肌膚如雪油宜。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,541評(píng)論 1 305
  • 那天怜姿,我揣著相機(jī)與錄音慎冤,去河邊找鬼。 笑死沧卢,一個(gè)胖子當(dāng)著我的面吹牛蚁堤,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播但狭,決...
    沈念sama閱讀 40,292評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼披诗,長吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了立磁?” 一聲冷哼從身側(cè)響起呈队,我...
    開封第一講書人閱讀 39,211評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎唱歧,沒想到半個(gè)月后宪摧,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,655評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡颅崩,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,846評(píng)論 3 336
  • 正文 我和宋清朗相戀三年几于,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片挨摸。...
    茶點(diǎn)故事閱讀 39,965評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡孩革,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出得运,到底是詐尸還是另有隱情膝蜈,我是刑警寧澤锅移,帶...
    沈念sama閱讀 35,684評(píng)論 5 347
  • 正文 年R本政府宣布,位于F島的核電站饱搏,受9級(jí)特大地震影響非剃,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜推沸,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,295評(píng)論 3 329
  • 文/蒙蒙 一备绽、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧鬓催,春花似錦肺素、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,894評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至课舍,卻和暖如春塌西,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背筝尾。 一陣腳步聲響...
    開封第一講書人閱讀 33,012評(píng)論 1 269
  • 我被黑心中介騙來泰國打工捡需, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人筹淫。 一個(gè)月前我還...
    沈念sama閱讀 48,126評(píng)論 3 370
  • 正文 我出身青樓站辉,卻偏偏與公主長得像,于是被迫代替她去往敵國和親贸街。 傳聞我的和親對(duì)象是個(gè)殘疾皇子庵寞,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,914評(píng)論 2 355