前言
這篇詳細(xì)介紹了Stream流的概念巩趁,創(chuàng)建方式,基本操作及部分源碼分析淳附∫槲浚可能有點(diǎn)長(zhǎng)哈,大家看起來(lái)比較費(fèi)勁奴曙,我自己寫(xiě)的也比較累别凹,光碼字就碼了很長(zhǎng)時(shí)間,大家看得過(guò)程中可以停下來(lái)休息下洽糟,喝個(gè)茶炉菲,斗個(gè)地主接著再來(lái)看,就安利到這里了坤溃,我也該洗洗睡了(碼字確實(shí)挺累的拍霜,寫(xiě)文章也是很累),最后在安利下哈薪介,個(gè)人絕得很詳細(xì)的祠饺,祝大家學(xué)習(xí)愉快哈,看完就可以找到心儀小姐姐(小哥哥)汁政。
1.流的概述及相關(guān)概念
java8提供了Stream API道偷,以流的方式來(lái)處理數(shù)據(jù)。那么到底什么是jdk8中所謂的流呢记劈,簡(jiǎn)單地講勺鸦,就是對(duì)數(shù)組以及集合等數(shù)據(jù)進(jìn)行加工處理,比如對(duì)數(shù)據(jù)過(guò)濾目木,映射祝旷,遍歷等操作。Stream流不是一種數(shù)據(jù)結(jié)構(gòu),不存儲(chǔ)數(shù)據(jù)怀跛,通過(guò)管道的方式獲取數(shù)據(jù)距贷,然后對(duì)數(shù)據(jù)進(jìn)行加工處理,并不會(huì)修改底層的數(shù)據(jù)源的吻谋。那么為什么要使用流呢忠蝗?因?yàn)槭褂昧骱蚻ambada表達(dá)式(流常與lambada表達(dá)式和函數(shù)式接口一起使用)對(duì)數(shù)據(jù)進(jìn)行操作的時(shí)候更加簡(jiǎn)潔,簡(jiǎn)單漓拾,同時(shí)流能利用現(xiàn)代多核CPU的特性妆棒,提高并行并發(fā)效率遮咖。流由以下三部分構(gòu)成:
- 源(集合,數(shù)組等數(shù)據(jù)源)
- 零個(gè)或多個(gè)中間操作,如filter狡逢,map等硕蛹,中間操作可以看做是流水線操作掺栅,每一個(gè)中間操作都可看做是一條流水線冗澈。中間操作執(zhí)行完后會(huì)產(chǎn)生一個(gè)新流,原來(lái)的數(shù)據(jù)源沒(méi)有變化示血。
- 終止操作棋傍,如forEach,count等,終止操作不會(huì)返回Stream類(lèi)型难审,可能不返回值瘫拣,也可能返回其他類(lèi)型的單個(gè)值
流操作的分類(lèi)
- 惰性求值,對(duì)應(yīng)于流的中間操作告喊,執(zhí)行后不直接產(chǎn)生結(jié)果
- 及早求值麸拄,對(duì)應(yīng)于流的終止操作,執(zhí)行后產(chǎn)生結(jié)果
舉個(gè)栗子:
public class StreamTest02 {
public static void main(String[] args) {
List<Integer> list = Arrays.asList(1,2,3,4,5,6,7,8,9);
//將list中的每個(gè)數(shù)乘以2黔姜,然后在求和
final Stream<Integer> integerStream = list.stream().map(i -> {
System.out.println("test----------------------------------");
return i * 2;
});
System.out.println(integerStream);
}
}
以上集合list就是Stream流的源拢切,map操作就是中間操作,對(duì)應(yīng)惰性求值地淀,如果后面沒(méi)有終止操作失球,map操作只是返回一個(gè)新的流岖是,但是并不執(zhí)行map中的操作帮毁,更看不到結(jié)果。想要map中的操作執(zhí)行豺撑,需要在后面加上終止操作烈疚。
public class StreamTest02 {
public static void main(String[] args) {
List<Integer> list = Arrays.asList(1,2,3,4,5,6,7,8,9);
//將list中的每個(gè)數(shù)乘以2,然后在求和
list.stream().map(i -> {
System.out.println("test-----------------------------");
return i * 2;
}).reduce(Integer::sum);
}
}
以上的reduce操作就是終止操作聪轿,對(duì)應(yīng)及早求值爷肝,執(zhí)行reduce終止操作時(shí),才一下子把所有的中間操作都執(zhí)行了。這時(shí)我們發(fā)現(xiàn)map中的操作執(zhí)行了灯抛,但是reduce歸約求和后的結(jié)果任然看不見(jiàn)金赦,這是因?yàn)槲覀儧](méi)有打印輸出結(jié)果,輸出就好了:
public class StreamTest02 {
public static void main(String[] args) {
List<Integer> list = Arrays.asList(1,2,3,4,5,6,7,8,9);
//將list中的每個(gè)數(shù)乘以2对嚼,然后在求和
list.stream().map(i -> {
System.out.println("test-----------------------------");
return i * 2;
}).reduce(Integer::sum).ifPresent(System.out::println);
}
}
流的分類(lèi):
- 串行流:stream
- 并行流:parallelStream
這里大致比較一下并行流和串行流的執(zhí)行效率:
1.使用串行流:
public class StreamTest{
public static void main(String[] args) {
List<String> list = new ArrayList<>(10000000);
for (int i = 0; i < 10000000; i++) {
list.add(UUID.randomUUID().toString());
}
System.out.println("開(kāi)始排序...");
long startTime = System.nanoTime();
list.stream().sorted().count();
long endTime = System.nanoTime();
long millis = TimeUnit.NANOSECONDS.toMillis(endTime - startTime);
System.out.println("排序耗時(shí)為:" + millis);
}
}
2.使用并行流:
public class StreamTes{
public static void main(String[] args) {
List<String> list = new ArrayList<>(10000000);
for (int i = 0; i < 10000000; i++) {
list.add(UUID.randomUUID().toString());
}
System.out.println("開(kāi)始排序...");
long startTime = System.nanoTime();
list.parallelStream().sorted().count();
long endTime = System.nanoTime();
long millis = TimeUnit.NANOSECONDS.toMillis(endTime - startTime);
System.out.println("排序耗時(shí)為:" + millis);
}
}
同樣是對(duì)一千萬(wàn)數(shù)進(jìn)行排序并統(tǒng)計(jì)元素個(gè)數(shù)夹抗,使用并行流要比使用串行流快了不少。是因?yàn)椴⑿辛鞒浞掷矛F(xiàn)代CPU的多核特性纵竖,使用多個(gè)線程并行地執(zhí)行任務(wù)漠烧,極大地提高了CPU的利用率,也提高了我們程序的執(zhí)行效率靡砌。
除了以上使用Collection接口中的stream和parallelStream方法來(lái)獲得并行流外已脓,還可以對(duì)流調(diào)用parallel()方法,parallel()是BaseStream接口中的一個(gè)方法通殃,源碼如下:
S parallel();
BaseStream接口中還提供了sequential()方法度液,表示一個(gè)串行流,使流能在串行流和并行流之間切換邓了。
提一下恨诱,Stream和BaseStream的關(guān)系是父子關(guān)系,Stream繼承了BaseStream接口骗炉。
栗子:
@Test
public void test(){
students.stream().parallel().filter(sudent ->sudent.getAge() > 16).limit(2).forEach(System.out::println);
}
如果不顯示聲明為parallel照宝,默認(rèn)是串行流。
注意:
- 流自己不存儲(chǔ)數(shù)據(jù)元素
- 流不改變數(shù)據(jù)源句葵,相反厕鹃,會(huì)返回一個(gè)持有結(jié)果的新的Stream
- 流的操作時(shí)延遲執(zhí)行的,需要結(jié)果的時(shí)候才執(zhí)行操作乍丈。
2.流的創(chuàng)建方式
2.1通過(guò)Stream接口的靜態(tài)方法of創(chuàng)建流剂碴,of方法源碼如下:
public static<T> Stream<T> of(T... values) {
return Arrays.stream(values);
}
栗子:
Stream<String> stream = Stream.of("beijing", "shanghai", "tianjin");
2.2通過(guò)數(shù)組的方式創(chuàng)建流
String[] arrays = new String[]{"beijing", "shanghai", "tianjin"};
Stream stringStream = Arrays.stream(arrays);
2.3通過(guò)集合方式創(chuàng)建流:
String[] arrays = new String[]{"beijing", "shanghai", "tianjin"};
List<String> list = Arrays.asList(arrays);
Stream stream = list.stream();
2.4通過(guò)Sream的迭代方法iterate創(chuàng)建無(wú)限流
iterate方法源碼分析:
public static<T> Stream<T> iterate(final T seed, final UnaryOperator<T> f) {...}
通過(guò)源碼可知,iterate方法有兩個(gè)參數(shù)轻专,第一個(gè)種子參數(shù)seed忆矛,表示從哪開(kāi)始,第一個(gè)要迭代的元素是哪個(gè)请垛。地二個(gè)參數(shù)是個(gè)函數(shù)式接口類(lèi)型的操作參數(shù)催训,UnaryOperator源碼如下:
@FunctionalInterface
public interface UnaryOperator<T> extends Function<T, T> {
static <T> UnaryOperator<T> identity() {
return t -> t;
}
}
UnaryOperator繼承于Function,給定一個(gè)參數(shù),返回結(jié)果宗收,不過(guò)UnaryOperator的參數(shù)和返回值類(lèi)型一樣都是T類(lèi)型漫拭。
舉個(gè)栗子:
Stream<Integer> integerStream = Stream.iterate(0,i -> i + 2);
//這里執(zhí)行一下終止操作forEach,遍歷下結(jié)果混稽,驗(yàn)證這是個(gè)無(wú)限流
integerStream.forEach(System.out::println);
以上創(chuàng)建的無(wú)限流如果不手動(dòng)停止運(yùn)行采驻,就會(huì)一直無(wú)限地執(zhí)行下去审胚,直到內(nèi)存爆滿(mǎn)異常退出。
如果要獲取有限個(gè)數(shù)的結(jié)果礼旅,可以加一個(gè)限制個(gè)數(shù)的中間操作limit操作:
Stream<Integer> integerStream = Stream.iterate(0,i -> i + 2);
integerStream.limit(10).forEach(System.out::println);
2.5通過(guò)Stream的generate靜態(tài)方法生成一個(gè)流
public static<T> Stream<T> generate(Supplier<T> s) {...}
由源碼可知膳叨,參數(shù)是一個(gè)Supplier類(lèi)型的函數(shù)式接口,Supplier不傳入任何參數(shù)痘系,但返回結(jié)果懒鉴。
栗子:
Stream.generate(Math::random).limit(10).forEach(System.out::println);
以上列出了創(chuàng)建Stream的五種方式,但Stream的創(chuàng)建不限于以上五種方法碎浇。
3.流的基本操作及源碼解析
Student類(lèi):
public class Student{
private String name;
private Integer age;
private double score;
private String level;
public String getLevel() {
return level;
}
public void setLevel(String level) {
this.level = level;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public double getScore() {
return score;
}
public void setScore(double score) {
this.score = score;
}
public Student(String name, Integer age, double score, String level) {
this.name = name;
this.age = age;
this.score = score;
this.level = level;
}
public Student() {
}
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", age=" + age +
", score=" + score +
", level='" + level + '\'' +
'}';
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Student student = (Student) o;
return age == student.age &&
Double.compare(student.score, score) == 0 &&
Objects.equals(name, student.name);
}
@Override
public int hashCode() {
return Objects.hash(name, age, score);
}
}
3.1中間操作:
3.1.1篩選與切片
filter--過(guò)濾临谱,接收l(shuí)ambada,從流中過(guò)濾掉不滿(mǎn)足給定條件的元素奴璃。源碼如下:
Stream<T> filter(Predicate<? super T> predicate);
filter的參數(shù)是一個(gè)Predicate的斷言類(lèi)型悉默,給定一個(gè)參數(shù),返回一個(gè)boolean值苟穆,判斷是否滿(mǎn)足要求抄课。
栗子:
@Test
public void test01(){
students.stream().filter(sudent ->sudent.getAge() > 16).forEach(System.out::println);
}
limit--截?cái)嗔鳎瞧湓夭怀^(guò)給定數(shù)量雳旅。
@Test
public void test03(){
//limit符合短路規(guī)則跟磨,找到兩條就不在向下遍歷
students.stream().filter(sudent ->sudent.getAge() > 16).limit(2).forEach(System.out::println);
}
skip(n)--跳過(guò)元素,返回一個(gè)跳過(guò)了前n個(gè)元素的流攒盈,若流中的元素個(gè)數(shù)不足n個(gè)抵拘,則返回一個(gè)空流。
@Test
public void test04(){
students.stream().filter(sudent ->sudent.getAge() > 16).skip(2).forEach(System.out::println);
}
distinct--篩選型豁,通過(guò)流所生成元素的hashCode()和equals()去除重復(fù)元素僵蛛。
@Test
public void test05(){
//distinct去重,使用distinct去重必須重寫(xiě)hashCode和equals方法
students.stream().filter(sudent ->sudent.getScore() < 60).distinct().forEach(System.out::println);
}
3.1.2映射
map--接收l(shuí)ambada迎变,將元素轉(zhuǎn)換成其他形式或提取信息充尉。接收一個(gè)函數(shù)作為參數(shù),該函數(shù)會(huì)被應(yīng)用到每個(gè)元素上衣形,并將其映射成一個(gè)新的元素驼侠,最后返回新流。源碼如下:
<R> Stream<R> map(Function<? super T, ? extends R> mapper);
map方法的參數(shù)是一個(gè)Function函數(shù)式接口類(lèi)型來(lái)表示我們要做的操作谆吴,給定一個(gè)參數(shù)倒源,返回結(jié)果,F(xiàn)unction的apply方法的參數(shù)和返回值類(lèi)型都是泛型類(lèi)型,map的返回值是一個(gè)帶泛型的Stream類(lèi)型纪铺,即返回一個(gè)新的流相速。
栗子:
@Test
public void test06(){
students.stream().map(Student::getName).forEach(System.out::println);
}
flatMap--扁平映射碟渺,接收一個(gè)函數(shù)作為參數(shù)鲜锚,將流中的每個(gè)元素都轉(zhuǎn)換成另一個(gè)流突诬,然后把所有流連接成一個(gè)流。源碼如下:
<R> Stream<R> flatMap(Function<? super T, ? extends Stream<? extends R>> mapper);
flatMap的參數(shù)也是一個(gè)Function類(lèi)型芜繁,不過(guò)和map不同的是旺隙,flatMap的Function接口的第二個(gè)泛型類(lèi)型不是R,而是Stream<? extends R>或它的子類(lèi)骏令。
在介紹flatMap之前蔬捷,我們先舉一個(gè)使用map處理的不太友好的栗子:
private static Stream<Character> filterCharacter(String str){
List<Character> list = new ArrayList<>();
for (char c : str.toCharArray()) {
list.add(c);
}
return list.stream();
}
@Test
public void test07(){
List<String> list = Arrays.asList("hello","world");
Stream<Stream<Character>> streamStream = list.stream().map(StreamTest04::filterCharacter);
streamStream.forEach((stream) -> stream.forEach(System.out::println));
}
以上,我們的filterCharacter方法返回的是一個(gè)流榔袋,而map也返回一個(gè)新流周拐,所以接收的類(lèi)型就是 Stream<Stream<Character>>,流中包含流凰兑,最外層的這個(gè)流包含兩個(gè)流元素妥粟,它們分別是流1[h,e,l,l,o]和流2[w,o,r,l,d],這對(duì)我們的遍歷不太方便,需要兩次遍歷才能遍歷到具體的元素吏够。
流這塊兒不太好調(diào)試勾给,所以我把代碼稍微修改一下,驗(yàn)證上面的結(jié)論:
@Test
public void test07(){
List<String> list = Arrays.asList("hello","world");
Stream<Stream<Character>> streamStream = list.stream().map(StreamTest04::filterCharacter);
streamStream.forEach(stream ->{
System.out.println(stream);
stream.forEach(System.out::println);
System.out.println("---------------------------------------------");
} );
}
使用flapMap
@Test
public void test08(){
List<String> list = Arrays.asList("hello","world");
final Stream<Character> characterStream = list.stream().flatMap(StreamTest04::filterCharacter);
characterStream.forEach(System.out::println);
}
flapMap將調(diào)用filterCharacter方法后生成的兩個(gè)映射流給它打碎了锅知,把每個(gè)映射流中以前的元素整合到一個(gè)新流中播急,這個(gè)新流為[h,e,l,l,o,w,o,r,l,d],并將新流返回。
以上的兩個(gè)映射方法返回的都是帶泛型的Stream類(lèi)型售睹,而泛型只能用包裝類(lèi)桩警,而不能用基本的數(shù)值類(lèi)型,比如int,long,double等昌妹,而只能用他們對(duì)應(yīng)的包裝類(lèi)型生真,這樣自動(dòng)地裝箱拆箱也會(huì)損耗一定的性能,所以Stream接口還提供了幾個(gè)可以使用基本數(shù)值類(lèi)型int捺宗,long,double的映射方法柱蟀,他們分別是mapToInt,mapToLong,mapToDouble。源碼分別如下:
IntStream mapToInt(ToIntFunction<? super T> mapper);
LongStream mapToLong(ToLongFunction<? super T> mapper);
DoubleStream mapToDouble(ToDoubleFunction<? super T> mapper);
以上三個(gè)方法的返回類(lèi)型分別為IntStream蚜厉,LongStream长已,DoubleStream。這三個(gè)接口就是為了方便我們使用基本數(shù)值類(lèi)型而設(shè)計(jì)的Stream類(lèi)型昼牛,里面也有一些使用的方法可以直接使用术瓮,這里不做介紹,感興趣的朋友可以看看源碼贰健。
舉個(gè)栗子:
Stream<Integer> stream = Stream.iterate(0, integer -> integer + 2).limit(10);
OptionalInt min = stream.filter(integer -> integer > 2).mapToInt(value -> value * 2).skip(2)
.limit(2).min();
min.ifPresent(System.out::println);
3.1.3排序
sorted()--自然排序
@Test
public void test09(){
List<String> list = Arrays.asList("a","b","c","d","e");
list.stream().sorted().forEach(System.out::println);
}
sorted(Comparator comparator)--定制排序
@Test
public void test10(){
students.stream().sorted((student1,student2) -> {
if(student1.getAge().equals(student2.getAge())){
return student1.getName().compareTo(student2.getName());
}else {
return student1.getAge().compareTo(student2.getAge());
}
}).forEach(System.out::println);
}
3.2終止操作
3.2.1查找與匹配
allMatch--檢查是否匹配所有元素
anyMatch--檢查是否至少一個(gè)元素
noneMatch--檢查是否沒(méi)有匹配所有元素
findFirst--返回第一個(gè)元素
findAny--返回當(dāng)前流中任意元素
count--返回流中元素的總個(gè)數(shù)
max--返回流中最大值
min--返回流中最小值
@Test
public void test11(){
//判斷是否所有的學(xué)生都小于30歲
final boolean b = students.stream().allMatch(student -> student.getAge() < 30);
System.out.println(b);
//判斷是否有學(xué)生小于15歲
System.out.println(students.stream().anyMatch(student -> student.getAge() < 15));
//判斷是否是否沒(méi)有學(xué)生的年齡小于10
System.out.println(students.stream().noneMatch(student -> student.getAge() < 10));
//獲取所有學(xué)生的成績(jī)由大到小排序后的第一個(gè)學(xué)生信息
Optional<Student> first = students.stream().sorted((s1, s2)
-> Double.compare(s2.getScore(), s1.getScore())).findFirst();
first.ifPresent(System.out::println);
//通過(guò)并行流獲取成績(jī)小于60的任意一個(gè)學(xué)生的信息
Optional<Student> any = students.parallelStream().filter(student -> student.getScore() < 60).findAny();
any.ifPresent(System.out::println);
//獲取所有學(xué)生的人數(shù)
System.out.println(students.stream().count());
//獲取成績(jī)最高的學(xué)生的信息
Optional<Student> max = students.stream().max(Comparator.comparingDouble(Student::getScore));
max.ifPresent(System.out::println);
//獲取成績(jī)最低的學(xué)生的成績(jī)
Optional<Double> min = students.stream().map(Student::getScore).min(Double::compareTo);
min.ifPresent(System.out::println);
}
3.2.2歸約
歸約(reduce)可以將流中元素通過(guò)一個(gè)計(jì)算函數(shù)計(jì)算后胞四,得到一個(gè)值
Stream源碼中有兩個(gè)reduce方法,一個(gè)是不可能為空伶椿,返回泛型類(lèi)型T辜伟;一個(gè)是結(jié)果可能為空氓侧,返回Optional<T>,避免空指針导狡。源碼如下:
1.結(jié)果不可能為空的情況:
T reduce(T identity, BinaryOperator<T> accumulator);
identity指從哪開(kāi)始計(jì)算约巷,accumulator累加器是一個(gè)BinaryOperator的函數(shù)式接口,源碼如下:
@FunctionalInterface
public interface BinaryOperator<T> extends BiFunction<T,T,T> {}
BinaryOperator繼承于BiFunction旱捧,只是它的兩個(gè)參數(shù)類(lèi)型和返回值類(lèi)型都是T類(lèi)型独郎。
栗子:
@Test
public void test13(){
List<Integer> list = Arrays.asList(1,2,3,4,5,6,7,8,9,10);
//結(jié)果不可能為空,所以不用封裝到Optional中
Integer sum = list.stream().reduce(0, Integer::sum);
System.out.println(sum);
}
Integer::sum通過(guò)靜態(tài)方法引用調(diào)用Integer類(lèi)中的sum方法求和枚赡,sum方法傳入兩個(gè)int值氓癌,返回求和結(jié)果,還是int值贫橙。源碼如下:
public static int sum(int a, int b) {
return a + b;
}
2.結(jié)果可能為空的情況:
Optional<T> reduce(BinaryOperator<T> accumulator);
栗子:
@Test
public void test12(){
//結(jié)果可能為空顽铸,所以封裝到Optional中
Optional<Double> optional = students.stream().map(Student::getScore).reduce(Double::sum);
optional.ifPresent(System.out::println);
}
3.2.3收集
collect--將流轉(zhuǎn)換為其他形式,接收一個(gè)Collector接口的實(shí)現(xiàn)料皇,用于給Stream中元素做匯總的方法谓松,collect源碼如下:
<R, A> R collect(Collector<? super T, A, R> collector);
Collector接口中提供了一些具體操作的函數(shù)式接口方法及其他方法:
public interface Collector<T, A, R> {
Supplier<A> supplier();
BiConsumer<A, T> accumulator();
BinaryOperator<A> combiner();
Function<A, R> finisher();
Set<Characteristics> characteristics();
}
不過(guò)我們一般不直接對(duì)Collector接口進(jìn)行操作,我們常用的是Collectors工具類(lèi)践剂,Collectors中定義了一個(gè)內(nèi)部類(lèi)鬼譬,這個(gè)內(nèi)部類(lèi)實(shí)現(xiàn)了Collector接口:
static class CollectorImpl<T, A, R> implements Collector<T, A, R> {
private final Supplier<A> supplier;
private final BiConsumer<A, T> accumulator;
private final BinaryOperator<A> combiner;
private final Function<A, R> finisher;
private final Set<Characteristics> characteristics;
...
}
這個(gè)工具類(lèi)中提供了一些靜態(tài)方法供我們使用,比如我們常用的toList方法逊脯,toSet方法优质,將流轉(zhuǎn)換為集合。
栗子:
@Test
public void test14(){
//將所有學(xué)生的姓名提取出來(lái)收集到一個(gè)集合中
List<String> list = students.stream().map(Student::getName).collect(Collectors.toList());
list.forEach(System.out::println);
}
如果Collectors中沒(méi)有我們想要將流轉(zhuǎn)換成的類(lèi)型军洼,還可以使用Collectors提供的toCollection方法:
@Test
public void test15(){
//將所有學(xué)生的姓名提取出來(lái)收集到一個(gè)集合中
Collection<String> collect = students.stream().map(Student::getName).collect(Collectors.toCollection(HashSet::new));
collect.forEach(System.out::println);
}
collect方法還有一個(gè)重載方法巩螃,只是這個(gè)重載方法用的不是很多,這里也分析一下哈匕争,感興趣的朋友可以看下哈避乏,不敢興趣,跳過(guò)即可甘桑。
<R> R collect(Supplier<R> supplier,
BiConsumer<R, ? super T> accumulator,
BiConsumer<R, R> combiner);
supplier拍皮,提供者函數(shù),是Supplier函數(shù)式接口類(lèi)型跑杭,不傳入任何參數(shù)铆帽,有一個(gè)返回值,這里是指新結(jié)果的容器德谅,也就是collect方法要返回的結(jié)果爹橱。
accumulator累加器函數(shù),是BiConsumer消費(fèi)式函數(shù)式接口類(lèi)型窄做,傳入兩個(gè)參數(shù)愧驱,不返回任何值慰技。這里用于對(duì)流中的每一個(gè)元素進(jìn)行遍歷,將遍歷到的每個(gè)元素合并到結(jié)果中冯键。
combiner,組合器函數(shù)庸汗,也是BiConsumer類(lèi)型惫确,與上面所不同的是,這個(gè)BiConsumer的兩個(gè)參數(shù)都是R類(lèi)型蚯舱,必須與累加器函數(shù)兼容改化,也就是要與累加器函數(shù)中第一個(gè)參數(shù)代表的的容器類(lèi)型相同。
舉個(gè)栗子:
Stream<String> stream = Stream.of("beijing", "shanghai", "tianjin");
List<String> list = stream.collect(() -> new ArrayList<>(),(theList,item) -> theList.add(item),
(theList1,theList2) -> theList1.addAll(theList2));
list.forEach(System.out::println);
上述代碼等價(jià)于下面使用方法引用代碼:
Stream<String> stream = Stream.of("beijing", "shanghai", "tianjin");
List<String> list = stream.collect(ArrayList::new, ArrayList::add, ArrayList::addAll);
list.forEach(System.out::println);
上面栗子我們是要把一個(gè)stream流轉(zhuǎn)化為一個(gè)集合容器枉昏,并將stream流流中原來(lái)的三個(gè)元素陈肛,biejing,shanghai,tianjin,轉(zhuǎn)變?yōu)樾碌募先萜鞯脑亍?/p>
轉(zhuǎn)換過(guò)程(collect三個(gè)參數(shù)具體運(yùn)作流程):
首先我們先需要用supplier提供者函數(shù)創(chuàng)建一個(gè)容器兄裂,來(lái)裝流中的三個(gè)元素句旱,最后返回的結(jié)果也是這個(gè)容器。
接下來(lái)我們就要用accumulator這個(gè)累加器函數(shù)來(lái)來(lái)遍歷stream流中的每個(gè)元素晰奖,并把每次遍歷到的元素都添加到一個(gè)集合中谈撒。這個(gè)集合相當(dāng)于一個(gè)中間集合,并不是我們上面用supplier提供者函數(shù)創(chuàng)建的要作為結(jié)果返回的集合容器匾南。
最后我們用combiner組合器函數(shù)把上面中間集合里的元素一次性地都添加到我們要返回結(jié)果的集合容器中啃匿,也就是將theList中的元素都添加到theList1中,theList1就相當(dāng)于supplier提供者函數(shù)創(chuàng)建的要作為結(jié)果返回的集合容器蛆楞,theList2就相當(dāng)于上面的theList.
下面我們來(lái)驗(yàn)證一下以上第二步的分析是否和執(zhí)行結(jié)果相同:
public class StreamTest03 {
public static void main(String[] args) {
Stream<String> stream = Stream.of("beijing", "shanghei", "tianjin");
List<String> list = stream.collect(() -> new ArrayList<>(),(theList,item) -> {
theList.add(item);
System.out.println(theList);
},
(theList1,theList2) -> theList1.addAll(theList2));
}
}
我們的流中有三個(gè)元素溯乒,所以在第二步中經(jīng)過(guò)了三次遍歷,每次遍歷到一個(gè)元素豹爹,并將每次遍歷到的元素添加到theList集合中裆悄,三次遍歷后theList集合的元素為:[beijing, shanghei, tianjin]。
怎么樣臂聋?是不是不太好理解灯帮,這個(gè)重載方法確實(shí)比較難容易理解,不過(guò)多琢磨琢磨逻住,結(jié)合著源碼和示例代碼練習(xí)一下還是會(huì)理解的钟哥,我學(xué)習(xí)這個(gè)方法也琢磨了挺長(zhǎng)時(shí)間。
再舉一例吧瞎访,jdk官方文檔的一個(gè)栗子:
String concat = stream.collect(StringBuilder::new, StringBuilder::append,
StringBuilder::append)
.toString();
我們稍微修改一下腻贰,把方法引用改成用lambada表達(dá)式更容易理解。
Stream<String> stream = Stream.of("beijing", "shanghai", "tianjin");
StringBuilder collect = stream.collect(() -> new StringBuilder(), (stringBuilder, item) -> stringBuilder.append(item),
(stringBuilder1, stringBuild2) -> stringBuilder1.append(stringBuild2));
System.out.println(collect.toString());
執(zhí)行結(jié)果是一樣的:
再看一下Collectors.toList()將流轉(zhuǎn)換為list集合的底層實(shí)現(xiàn)扒秸,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);
}
看到這里我們應(yīng)該恍然大悟了吧播演,原來(lái)Collectors.toList()方法用的就是我們上面分析的帶三個(gè)參數(shù)的collect的重載方法實(shí)現(xiàn)的冀瓦,二者的本質(zhì)是一樣的。
除了以上的toList,toConllection,toSet方法以外写烤,Collectors工具類(lèi)還為我們提供了一些其他的實(shí)用的方法翼闽,比如求總數(shù),平均數(shù)洲炊,最大值感局,最小值,分組暂衡,分區(qū)等询微,下面就介紹一些我們比較常用的方法。
@Test
public void test16(){
//獲取學(xué)生人數(shù)
Long count = students.stream().collect(Collectors.counting());
System.out.println(count);
//求學(xué)生成績(jī)的平均分
Double averageScore = students.stream().collect(Collectors.averagingDouble(Student::getScore));
System.out.println(averageScore);
//求所有學(xué)生總成績(jī)
double sumScore = students.stream().collect(Collectors.summingDouble(Student::getScore));
System.out.println(sumScore);
//獲取成績(jī)最高的學(xué)生信息
Optional<Student> max = students.stream().collect(Collectors.maxBy((s1, s2)
-> Double.compare(s1.getScore(), s2.getScore())));
max.ifPresent(System.out::println);
//獲取成績(jī)最底的學(xué)生的成績(jī)
Optional<Double> min = students.stream().map(Student::getScore).collect(Collectors.minBy(Double::compareTo));
min.ifPresent(System.out::println);
//按照l(shuí)evel將所有學(xué)生分組
Map<String, List<Student>> listMap = students.stream().collect(Collectors.groupingBy(Student::getLevel));
listMap.forEach((level, students) -> {
System.out.println(level+":"+students);
});
System.out.println();
Map<String, Long> collect = students.stream().collect(Collectors.groupingBy(Student::getLevel, Collectors.counting()));
collect.forEach((level,counts) -> System.out.println(level + ":" + counts));
System.out.println();
//分區(qū)狂巢,成績(jī)大于等于60為及格區(qū)撑毛,小于60為不及格區(qū)
Map<Boolean, List<Student>> booleanListMap = students.stream().collect(Collectors.partitioningBy(student
-> student.getScore() >= 60));
booleanListMap.forEach((bool, students) -> System.out.println(bool + ":" + students));
}
這樣使用Stream流是不是有點(diǎn)像寫(xiě)sql語(yǔ)句,其實(shí)jdk8中的stream流挺類(lèi)似于sql語(yǔ)句的唧领,都是一種描述性語(yǔ)言藻雌,讓開(kāi)發(fā)者只需要寫(xiě)出想要的結(jié)果對(duì)應(yīng)的語(yǔ)句,而不用關(guān)心這些語(yǔ)句的底層是怎么實(shí)現(xiàn)和執(zhí)行的斩个。
同時(shí)Collectors還有一類(lèi)統(tǒng)計(jì)方法蹦疑,用于統(tǒng)計(jì)個(gè)數(shù),總數(shù)萨驶,最大值歉摧,最小值以及求平均數(shù)等,這類(lèi)方法有summarizingInt腔呜,summarizingLong叁温,summarizingDouble,分別對(duì)應(yīng)于數(shù)值類(lèi)型的整型核畴,長(zhǎng)整型膝但,double類(lèi)型。以summarizingDouble為例介紹下如何使用谤草。
@Test
public void test17(){
//統(tǒng)計(jì)
DoubleSummaryStatistics summaryStatistics = students.stream()
.collect(Collectors.summarizingDouble(Student::getScore));
System.out.println(summaryStatistics.getCount());
System.out.println(summaryStatistics.getSum());
System.out.println(summaryStatistics.getMax());
System.out.println(summaryStatistics.getMin());
System.out.println(summaryStatistics.getAverage());
}
隨后一個(gè)方法跟束,連接方法:
@Test
public void test18(){
//連接
String collect = students.stream().map(Student::getName).collect(Collectors.joining(","));
System.out.println(collect);
}
4.注意
提一點(diǎn)使用流的注意事項(xiàng),就是我們?cè)谑褂昧鲿r(shí)要注意流是不能被重復(fù)使用或消費(fèi)的丑孩。不然會(huì)報(bào)下面的異常冀宴。
stream has already been operated upon or closed
栗子:
public class StreamTest06 {
public static void main(String[] args) {
Stream<Integer> stream = Stream.iterate(0, integer -> integer + 2).limit(10);
System.out.println(stream);
System.out.println(stream.filter(integer -> integer > 2));
//stream has already been operated upon or closed,因?yàn)楹蜕厦媸褂玫氖峭粋€(gè)流對(duì)象,而一個(gè)流的對(duì)象不能被重復(fù)使用
System.out.println(stream.distinct());//和上面用的同一個(gè)stream
}
}
修改一下使之用不同的stream:
public class StreamTest06 {
public static void main(String[] args) {
Stream<Integer> stream = Stream.iterate(0, integer -> integer + 2).limit(10);
System.out.println(stream);
Stream<Integer> integerStream = stream.filter(integer -> integer > 2);
System.out.println(integerStream);
System.out.println(integerStream.distinct());
}
}
5.總結(jié)
終于寫(xiě)完了温学,是不是很啰嗦略贮,太長(zhǎng)了,為了保持內(nèi)容的連續(xù)性就沒(méi)有拆分成幾篇文章,腦瓜疼逃延,難免有介紹的不到位的地方览妖,也可能有錯(cuò)別字,雖然我已經(jīng)檢查了好幾遍揽祥,大家就湊活看吧讽膏,能對(duì)大家的學(xué)習(xí)起到點(diǎn)幫助作用就可以了,同時(shí)我自己也可以回過(guò)頭來(lái)復(fù)習(xí)已經(jīng)忘記的知識(shí)拄丰。如果大家發(fā)現(xiàn)有不對(duì)的地方府树,歡迎留言指正,錯(cuò)別字也可以愈案。輸出不易挺尾,大家給點(diǎn)個(gè)贊也行(皮一下哈)鹅搪,同時(shí)也歡迎大家關(guān)注微信公眾號(hào)站绪,后期會(huì)有更多的輸出,共同學(xué)習(xí)丽柿,不斷進(jìn)步恢准,持續(xù)輸出。