jdk8 Stream流
1.Stream API 簡(jiǎn)介
Stream API是Java 8中加入的一套新的API彼念,主要用于處理集合操作缓苛,不過(guò)它的處理方式與傳統(tǒng)的方式不同井誉,
稱為“數(shù)據(jù)流處理”赴蝇。流(Stream)類似于關(guān)系數(shù)據(jù)庫(kù)的查詢操作闰靴,是一種聲明式操作。比如要從數(shù)據(jù)庫(kù)中獲取所有年齡大于20歲的用戶的名稱撒顿,
并按照用戶的創(chuàng)建時(shí)間進(jìn)行排序丑罪,如果在sql中就會(huì)很容易完成荚板,但是在java程序中凤壁,在jdk8以前可能要使用很多的if條件吩屹,但是在jdk8的stream
流中,我們可以這樣:
List<String> userNames =
users.stream()
.filter(user -> user.getAge() > 20)
.sorted(comparing(User::getAddDate))
.map(User::getUserName)
.collect(toList());
在Java中拧抖,集合是一種數(shù)據(jù)結(jié)構(gòu)煤搜,或者說(shuō)是一種容器,用于存放數(shù)據(jù)唧席,流不是容器擦盾,它不關(guān)心數(shù)據(jù)的存放,只關(guān)注如何處理淌哟。
在遍歷一個(gè)數(shù)組的時(shí)候迹卢,我們回采用for-each
的方式,這種屬于外部遍歷,而流使用的是內(nèi)部遍歷的方式徒仓,也就是在內(nèi)部幫你把邏輯給處理好了
可以用一下的方式:
// 外部
List<String> list = Arrays.asList("A", "B", "C", "D");
for (String str : list) {
System.out.println(str);
}
// 內(nèi)部
list.stream().forEach(System.out::println);
其實(shí)在stream流的內(nèi)部腐碱,Stream API將迭代操作封裝到了內(nèi)部,它會(huì)自動(dòng)的選擇最優(yōu)的迭代方式掉弛,并且使用并行方式處理時(shí)症见,
將集合分成多段,每一段分別使用不同的線程處理殃饿,最后將處理結(jié)果合并輸出谋作。
需要注意的是,每個(gè)流的只能去遍歷一次乎芳,如果對(duì)一個(gè)流遍歷兩次遵蚜,會(huì)拋出java.lang.IllegalStateException
異常
List<String> list = Arrays.asList("A", "B", "C", "D");
Stream<String> stream = list.stream();
stream.forEach(System.out::println);
stream.forEach(System.out::println); // 這里會(huì)拋出java.lang.IllegalStateException異常,因?yàn)榱饕呀?jīng)被關(guān)閉
流通常是有三個(gè)部分組成
1.數(shù)據(jù)源:流的獲取奈惑,比如list.stream()
方法谬晕;
2.中間處理:中間處理是對(duì)流元素的一系列處理。比如過(guò)濾filter
携取,排序sorted
攒钳,映射map
;
3.終端處理:終端處理會(huì)生成結(jié)果雷滋,結(jié)果可以是任何不是流值不撑。生成List,可用collect(Collectors.toList())
,生成Map可用collect(Collectors.toMap())
也可以不返回結(jié)果,如stream.forEach(System.out::println)
就是將結(jié)果打印到控制臺(tái)中晤斩,并沒(méi)有返回焕檬。
2.使用流
-
filter()
:對(duì)元素進(jìn)行過(guò)濾 -
map()
:將流的元素映射成另一個(gè)類型 -
distinct()
:去除流中重復(fù)的元素 -
sorted()
:對(duì)元素進(jìn)行排序 -
forEach
:對(duì)流中的每個(gè)元素執(zhí)行某個(gè)操作 -
peek()
:與forEach()方法效果類似,不同的是澳泵,該方法會(huì)返回一個(gè)新的流实愚,而forEach()無(wú)返回 -
limit()
:截取流中前面幾個(gè)元素 -
skip()
:跳過(guò)流中前面幾個(gè)元素 -
toArray()
:將流轉(zhuǎn)換為數(shù)組 -
reduce()
:對(duì)流中的元素歸約操作,將每個(gè)元素合起來(lái)形成一個(gè)新的值 -
collect()
:對(duì)流的匯總操作,比如輸出成List集合 -
anyMatch()
:匹配流中的元素腊敲,類似的操作還有allMatch()和noneMatch()方法 -
findFirst()
:查找第一個(gè)元素击喂,類似的還有findAny()方法 -
max()
:求最大值 -
min()
:求最小值 -
count()
:求總數(shù)
使用方法
2.1過(guò)濾和排序
Stream.of(1, 8, 5, 2, 1, 0, 9, 2, 0, 4, 8)
.filter(n -> n > 2) // 對(duì)元素過(guò)濾,保留大于2的元素
.distinct() // 去重碰辅,類似于SQL語(yǔ)句中的DISTINCT
.skip(1) // 跳過(guò)前面1個(gè)元素
.limit(2) // 返回開(kāi)頭2個(gè)元素懂昂,類似于SQL語(yǔ)句中的SELECT TOP
.sorted() // 對(duì)結(jié)果排序
.forEach(System.out::println);
//filter后剩下:8,5没宾,9凌彬,4,8
//去重后排序剩下:5循衰,9
2.2查找和匹配
Stream中提供的查找方法有anyMatch()铲敛、allMatch()、noneMatch()会钝、findFirst()原探、findAny()
,
這些方法被用來(lái)查找或匹配某些元素是否符合給定的條件:
boolean hasMatch = Stream.of("Java", "C#", "PHP", "C++", "Python")
.anyMatch(s -> s.equals("Java"));
// hasMatch:true
boolean hasAllMatch = Stream.of("Java", "C#", "PHP", "C++", "Python")
.allMatch(s -> s.contains("#"));
//hasAllMatch:false
Optional<String> element = Stream.of("Java", "C#", "PHP", "C++", "Python")
.filter(s -> s.contains("C"))
// .findFirst() // 查找第一個(gè)元素
.findAny(); // 查找任意元素
//element:Optional[C#]
實(shí)際上測(cè)試結(jié)果發(fā)現(xiàn)顽素,findFirst()
和findAny()
返回的都是第一個(gè)元素,兩者之間到底有沒(méi)什么區(qū)別呢咽弦,
通過(guò)查看javadoc描述,大致意思是findAny()
是為了提高并行操作時(shí)的性能胁出,所以在使用中如果沒(méi)有特殊需求型型,都是
建議使用findAny()
2.3 歸約
歸約操作就是將流中的元素進(jìn)行合并,形成一個(gè)新的值全蝶,常見(jiàn)的歸約操作包括求和等運(yùn)算裂垦,
求最大值或最小值烈掠。歸約操作一般使用reduce()
方法眨唬,
與map()
方法搭配使用卸例,可以處理一些很復(fù)雜的歸約操作。
List<Book> books =Arrays.asList(
new Book("Java編程思想", "Bruce Eckel", "機(jī)械工業(yè)出版社", 108.00D),
new Book("Java 8實(shí)戰(zhàn)", "Mario Fusco", "人民郵電出版社", 79.00D),
new Book("MongoDB權(quán)威指南(第2版)", "Kristina Chodorow", "人民郵電出版社", 69.00D)
);
// 計(jì)算所有圖書的總價(jià)
Optional<Double> totalPrice = books.stream()
.map(Book::getPrice)
.reduce((n, m) -> n + m);
// 價(jià)格最高的圖書
Optional<Book> expensive = books.stream().max(Comparator.comparing(Book::getPrice));
// 價(jià)格最低的圖書
Optional<Book> cheapest = books.stream().min(Comparator.comparing(Book::getPrice));
// 計(jì)算總數(shù)
long count = books.stream().count();
在計(jì)算圖書總價(jià)的時(shí)候首先使用map()
方法得到所有圖書價(jià)格的流始苇,然后再使用reduce()
方法進(jìn)行歸約計(jì)算砌烁。
3.數(shù)據(jù)收集
數(shù)據(jù)收集是流式數(shù)據(jù)處理的終端處理,與中間處理不同的是催式,終端處理會(huì)消耗流函喉,
也就是說(shuō),終端處理之后荣月,這個(gè)流就會(huì)被關(guān)閉管呵,如果再進(jìn)行中間處理,就會(huì)拋出異常哺窄。數(shù)據(jù)收集主要使用collect
方法捐下,
該方法也屬于歸約操作账锹,像reduce()
方法那樣可以接收各種做法作為參數(shù),將流中的元素累積成一個(gè)匯總結(jié)果坷襟,
具體的做法是通過(guò)定義新的Collector
接口來(lái)定義的奸柬。
3.1規(guī)約和匯總
// 求和
long count = books.stream().collect(counting());
// 價(jià)格最高的圖書
Optional<Book> expensive = books.stream().collect(maxBy(comparing(Book::getPrice)));
// 價(jià)格最低的圖書
Optional<Book> cheapest = books.stream().collect(minBy(comparing(Book::getPrice)));
上面的代碼假設(shè)你已經(jīng)使用靜態(tài)導(dǎo)入了Collectors
和Comparator
兩個(gè)類,
這樣你就不用再去寫Collectors.counting()
和Comparator.comparing()
這樣的代碼了:
import static java.util.stream.Collectors.*;
import static java.util.Comparator.*;
Collectors
類還包含一個(gè)joining()
方法啤握,該方法用于連接字符串:
String str = Stream.of("A", "B", "C", "D").collect(joining(","));
3.2分組
和關(guān)系型數(shù)據(jù)庫(kù)類似,流也提供了類似數(shù)據(jù)庫(kù)的group by的特性晶框,由Collectors.groupingBy()
方法提供:
Map<String, List<Book>> booksGroup = books.stream().collect(groupingBy(Book::getPublisher));
上面的代碼按照出版社對(duì)圖書進(jìn)行分組排抬,分組的結(jié)果是一個(gè)Map
對(duì)象
,Map
的key
值是出版社的名稱授段,value
值是每個(gè)出版社分組對(duì)應(yīng)的集合蹲蒲。
分組方法groupingBy()
接收一個(gè)Function
接口作為參數(shù),
上面的例子中我們使用了方法引用傳遞了出版社作為分組的依據(jù)
侵贵,但實(shí)際情況可能比這復(fù)雜届搁,比如將價(jià)格在0-50之間的書籍分成一組,50-100之間的分成一組窍育,
超過(guò)100的分成一組卡睦,這時(shí)候,我們可以直接使用Lambda表達(dá)式來(lái)表示這個(gè)分組邏輯:
// 獲取流
List<Book> books = Arrays.asList(
new Book("Java編程思想", "Bruce Eckel", "機(jī)械工業(yè)出版社", 108.00D),
new Book("Java 8實(shí)戰(zhàn)", "Mario Fusco", "人民郵電出版社", 79.00D),
new Book("MongoDB權(quán)威指南(第2版)", "Kristina Chodorow", "人民郵電出版社", 69.00D),
new Book("MongoDB", "Kristina Chodorow", "人民郵電出版社", 50.00D),
new Book("MongoDB", "Kristina Chodorow", "人民郵電出版社", 50.00D)
);
Map<String, List<Book>> booksGroup = books
.stream()
.collect(groupingBy(book -> {
if (book.getPrice() > 0 && book.getPrice() <= 50) {
return "A";
} else if (book.getPrice() > 50 && book.getPrice() <=100) {
return "B";
} else {
return "C";
}
}));
//輸出結(jié)果
{A=[Book{name='MongoDB', author='Kristina Chodorow', address='人民郵電出版社', price=50.0}, Book{name='MongoDB', author='Kristina Chodorow', address='人民郵電出版社', price=50.0}],
B=[Book{name='Java 8實(shí)戰(zhàn)', author='Mario Fusco', address='人民郵電出版社', price=79.0}, Book{name='MongoDB權(quán)威指南(第2版)', author='Kristina Chodorow', address='人民郵電出版社', price=69.0}],
C=[Book{name='Java編程思想', author='Bruce Eckel', address='機(jī)械工業(yè)出版社', price=108.0}]}
groupingBy()
方法還支持多級(jí)分組漱抓,
他有一個(gè)重載方法表锻,除了接收一個(gè)Function
類型的參數(shù)外,還接收一個(gè)Collector
類型的參數(shù):
Map<String, Map<String, List<Book>>> booksGroup = books.stream().collect(
groupingBy(Book::getPublisher, groupingBy(book -> {
if (book.getPrice() > 0 && book.getPrice() <= 50) {
return "A";
} else if (book.getPrice() > 50 && book.getPrice() <=100) {
return "B";
} else {
return "C";
}
}))
);
//輸出結(jié)果
{Mario Fusco={B=[Book{name='Java 8實(shí)戰(zhàn)', author='Mario Fusco', address='人民郵電出版社', price=79.0}]},
Bruce Eckel={C=[Book{name='Java編程思想', author='Bruce Eckel', address='機(jī)械工業(yè)出版社', price=108.0}]},
Kristina Chodorow={A=[Book{name='MongoDB', author='Kristina Chodorow', address='人民郵電出版社', price=50.0},
Book{name='MongoDB', author='Kristina Chodorow', address='人民郵電出版社', price=50.0}],
B=[Book{name='MongoDB權(quán)威指南(第2版)', author='Kristina Chodorow', address='人民郵電出版社', price=69.0}]}}
groupingBy()
的第二個(gè)參數(shù)可以是任意類型乞娄,只要是Collector
接口的實(shí)例就可以瞬逊,比如先分組,再統(tǒng)計(jì)數(shù)量:
Map<String, Long> countGroup = books.stream()
.collect(groupingBy(Book::getPublisher, counting()));
還可以獲取分組后每組價(jià)格最高的的圖書:
Map<String, Book> expensiveGroup = books.stream()
.collect(groupingBy(Book::getAuthor, collectingAndThen(
maxBy(comparingDouble(Book::getPrice)),
Optional::get
)));
//輸入結(jié)果
/*{Mario Fusco=Book{name='Java 8實(shí)戰(zhàn)', author='Mario Fusco', address='人民郵電出版社', price=79.0},
Bruce Eckel=Book{name='Java編程思想', author='Bruce Eckel', address='機(jī)械工業(yè)出版社', price=108.0},
Kristina Chodorow=Book{name='MongoDB', author='Kristina Chodorow', address='人民郵電出版社', price=80.0}}*/