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