前言
這篇詳細介紹了Stream流的概念,創(chuàng)建方式,基本操作及部分源碼分析??赡苡悬c長哈,大家看起來比較費勁,我自己寫的也比較累,光碼字就碼了很長時間,大家看得過程中可以停下來休息下,喝個茶,斗個地主接著再來看,就安利到這里了,我也該洗洗睡了(碼字確實挺累的,寫文章也是很累),最后在安利下哈,個人絕得很詳細的,祝大家學習愉快哈,看完就可以找到心儀小姐姐(小哥哥)。
1.流的概述及相關概念
java8提供了Stream API,以流的方式來處理數(shù)據(jù)。那么到底什么是jdk8中所謂的流呢,簡單地講,就是對數(shù)組以及集合等數(shù)據(jù)進行加工處理,比如對數(shù)據(jù)過濾,映射,遍歷等操作。Stream流不是一種數(shù)據(jù)結(jié)構(gòu),不存儲數(shù)據(jù),通過管道的方式獲取數(shù)據(jù),然后對數(shù)據(jù)進行加工處理,并不會修改底層的數(shù)據(jù)源的。那么為什么要使用流呢?因為使用流和lambada表達式(流常與lambada表達式和函數(shù)式接口一起使用)對數(shù)據(jù)進行操作的時候更加簡潔,簡單,同時流能利用現(xiàn)代多核CPU的特性,提高并行并發(fā)效率。流由以下三部分構(gòu)成:
- 源(集合,數(shù)組等數(shù)據(jù)源)
- 零個或多個中間操作,如filter,map等,中間操作可以看做是流水線操作,每一個中間操作都可看做是一條流水線。中間操作執(zhí)行完后會產(chǎn)生一個新流,原來的數(shù)據(jù)源沒有變化。
- 終止操作,如forEach,count等,終止操作不會返回Stream類型,可能不返回值,也可能返回其他類型的單個值
流操作的分類
- 惰性求值,對應于流的中間操作,執(zhí)行后不直接產(chǎn)生結(jié)果
- 及早求值,對應于流的終止操作,執(zhí)行后產(chǎn)生結(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中的每個數(shù)乘以2,然后在求和
final Stream<Integer> integerStream = list.stream().map(i -> {
System.out.println("test----------------------------------");
return i * 2;
});
System.out.println(integerStream);
}
}

以上集合list就是Stream流的源,map操作就是中間操作,對應惰性求值,如果后面沒有終止操作,map操作只是返回一個新的流,但是并不執(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中的每個數(shù)乘以2,然后在求和
list.stream().map(i -> {
System.out.println("test-----------------------------");
return i * 2;
}).reduce(Integer::sum);
}
}

以上的reduce操作就是終止操作,對應及早求值,執(zhí)行reduce終止操作時,才一下子把所有的中間操作都執(zhí)行了。這時我們發(fā)現(xiàn)map中的操作執(zhí)行了,但是reduce歸約求和后的結(jié)果任然看不見,這是因為我們沒有打印輸出結(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中的每個數(shù)乘以2,然后在求和
list.stream().map(i -> {
System.out.println("test-----------------------------");
return i * 2;
}).reduce(Integer::sum).ifPresent(System.out::println);
}
}

流的分類:
- 串行流: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("開始排序...");
long startTime = System.nanoTime();
list.stream().sorted().count();
long endTime = System.nanoTime();
long millis = TimeUnit.NANOSECONDS.toMillis(endTime - startTime);
System.out.println("排序耗時為:" + 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("開始排序...");
long startTime = System.nanoTime();
list.parallelStream().sorted().count();
long endTime = System.nanoTime();
long millis = TimeUnit.NANOSECONDS.toMillis(endTime - startTime);
System.out.println("排序耗時為:" + millis);
}
}

同樣是對一千萬數(shù)進行排序并統(tǒng)計元素個數(shù),使用并行流要比使用串行流快了不少。是因為并行流充分利用現(xiàn)代CPU的多核特性,使用多個線程并行地執(zhí)行任務,極大地提高了CPU的利用率,也提高了我們程序的執(zhí)行效率。
除了以上使用Collection接口中的stream和parallelStream方法來獲得并行流外,還可以對流調(diào)用parallel()方法,parallel()是BaseStream接口中的一個方法,源碼如下:
S parallel();
BaseStream接口中還提供了sequential()方法,表示一個串行流,使流能在串行流和并行流之間切換。
提一下,Stream和BaseStream的關系是父子關系,Stream繼承了BaseStream接口。
栗子:
@Test
public void test(){
students.stream().parallel().filter(sudent ->sudent.getAge() > 16).limit(2).forEach(System.out::println);
}
如果不顯示聲明為parallel,默認是串行流。
注意:
- 流自己不存儲數(shù)據(jù)元素
- 流不改變數(shù)據(jù)源,相反,會返回一個持有結(jié)果的新的Stream
- 流的操作時延遲執(zhí)行的,需要結(jié)果的時候才執(zhí)行操作。
2.流的創(chuàng)建方式
2.1通過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通過數(shù)組的方式創(chuàng)建流
String[] arrays = new String[]{"beijing", "shanghai", "tianjin"};
Stream stringStream = Arrays.stream(arrays);
2.3通過集合方式創(chuàng)建流:
String[] arrays = new String[]{"beijing", "shanghai", "tianjin"};
List<String> list = Arrays.asList(arrays);
Stream stream = list.stream();
2.4通過Sream的迭代方法iterate創(chuàng)建無限流
iterate方法源碼分析:
public static<T> Stream<T> iterate(final T seed, final UnaryOperator<T> f) {...}
通過源碼可知,iterate方法有兩個參數(shù),第一個種子參數(shù)seed,表示從哪開始,第一個要迭代的元素是哪個。地二個參數(shù)是個函數(shù)式接口類型的操作參數(shù),UnaryOperator源碼如下:
@FunctionalInterface
public interface UnaryOperator<T> extends Function<T, T> {
static <T> UnaryOperator<T> identity() {
return t -> t;
}
}
UnaryOperator繼承于Function,給定一個參數(shù),返回結(jié)果,不過UnaryOperator的參數(shù)和返回值類型一樣都是T類型。
舉個栗子:
Stream<Integer> integerStream = Stream.iterate(0,i -> i + 2);
//這里執(zhí)行一下終止操作forEach,遍歷下結(jié)果,驗證這是個無限流
integerStream.forEach(System.out::println);

以上創(chuàng)建的無限流如果不手動停止運行,就會一直無限地執(zhí)行下去,直到內(nèi)存爆滿異常退出。
如果要獲取有限個數(shù)的結(jié)果,可以加一個限制個數(shù)的中間操作limit操作:
Stream<Integer> integerStream = Stream.iterate(0,i -> i + 2);
integerStream.limit(10).forEach(System.out::println);

2.5通過Stream的generate靜態(tài)方法生成一個流
public static<T> Stream<T> generate(Supplier<T> s) {...}
由源碼可知,參數(shù)是一個Supplier類型的函數(shù)式接口,Supplier不傳入任何參數(shù),但返回結(jié)果。
栗子:
Stream.generate(Math::random).limit(10).forEach(System.out::println);
以上列出了創(chuàng)建Stream的五種方式,但Stream的創(chuàng)建不限于以上五種方法。
3.流的基本操作及源碼解析
Student類:
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--過濾,接收lambada,從流中過濾掉不滿足給定條件的元素。源碼如下:
Stream<T> filter(Predicate<? super T> predicate);
filter的參數(shù)是一個Predicate的斷言類型,給定一個參數(shù),返回一個boolean值,判斷是否滿足要求。
栗子:
@Test
public void test01(){
students.stream().filter(sudent ->sudent.getAge() > 16).forEach(System.out::println);
}
limit--截斷流,是其元素不超過給定數(shù)量。
@Test
public void test03(){
//limit符合短路規(guī)則,找到兩條就不在向下遍歷
students.stream().filter(sudent ->sudent.getAge() > 16).limit(2).forEach(System.out::println);
}
skip(n)--跳過元素,返回一個跳過了前n個元素的流,若流中的元素個數(shù)不足n個,則返回一個空流。
@Test
public void test04(){
students.stream().filter(sudent ->sudent.getAge() > 16).skip(2).forEach(System.out::println);
}
distinct--篩選,通過流所生成元素的hashCode()和equals()去除重復元素。
@Test
public void test05(){
//distinct去重,使用distinct去重必須重寫hashCode和equals方法
students.stream().filter(sudent ->sudent.getScore() < 60).distinct().forEach(System.out::println);
}
3.1.2映射
map--接收lambada,將元素轉(zhuǎn)換成其他形式或提取信息。接收一個函數(shù)作為參數(shù),該函數(shù)會被應用到每個元素上,并將其映射成一個新的元素,最后返回新流。源碼如下:
<R> Stream<R> map(Function<? super T, ? extends R> mapper);
map方法的參數(shù)是一個Function函數(shù)式接口類型來表示我們要做的操作,給定一個參數(shù),返回結(jié)果,F(xiàn)unction的apply方法的參數(shù)和返回值類型都是泛型類型,map的返回值是一個帶泛型的Stream類型,即返回一個新的流。
栗子:
@Test
public void test06(){
students.stream().map(Student::getName).forEach(System.out::println);
}
flatMap--扁平映射,接收一個函數(shù)作為參數(shù),將流中的每個元素都轉(zhuǎn)換成另一個流,然后把所有流連接成一個流。源碼如下:
<R> Stream<R> flatMap(Function<? super T, ? extends Stream<? extends R>> mapper);
flatMap的參數(shù)也是一個Function類型,不過和map不同的是,flatMap的Function接口的第二個泛型類型不是R,而是Stream<? extends R>或它的子類。
在介紹flatMap之前,我們先舉一個使用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方法返回的是一個流,而map也返回一個新流,所以接收的類型就是 Stream<Stream<Character>>,流中包含流,最外層的這個流包含兩個流元素,它們分別是流1[h,e,l,l,o]和流2[w,o,r,l,d],這對我們的遍歷不太方便,需要兩次遍歷才能遍歷到具體的元素。
流這塊兒不太好調(diào)試,所以我把代碼稍微修改一下,驗證上面的結(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方法后生成的兩個映射流給它打碎了,把每個映射流中以前的元素整合到一個新流中,這個新流為[h,e,l,l,o,w,o,r,l,d],并將新流返回。
以上的兩個映射方法返回的都是帶泛型的Stream類型,而泛型只能用包裝類,而不能用基本的數(shù)值類型,比如int,long,double等,而只能用他們對應的包裝類型,這樣自動地裝箱拆箱也會損耗一定的性能,所以Stream接口還提供了幾個可以使用基本數(shù)值類型int,long,double的映射方法,他們分別是mapToInt,mapToLong,mapToDouble。源碼分別如下:
IntStream mapToInt(ToIntFunction<? super T> mapper);
LongStream mapToLong(ToLongFunction<? super T> mapper);
DoubleStream mapToDouble(ToDoubleFunction<? super T> mapper);
以上三個方法的返回類型分別為IntStream,LongStream,DoubleStream。這三個接口就是為了方便我們使用基本數(shù)值類型而設計的Stream類型,里面也有一些使用的方法可以直接使用,這里不做介紹,感興趣的朋友可以看看源碼。
舉個栗子:
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--檢查是否至少一個元素
noneMatch--檢查是否沒有匹配所有元素
findFirst--返回第一個元素
findAny--返回當前流中任意元素
count--返回流中元素的總個數(shù)
max--返回流中最大值
min--返回流中最小值
@Test
public void test11(){
//判斷是否所有的學生都小于30歲
final boolean b = students.stream().allMatch(student -> student.getAge() < 30);
System.out.println(b);
//判斷是否有學生小于15歲
System.out.println(students.stream().anyMatch(student -> student.getAge() < 15));
//判斷是否是否沒有學生的年齡小于10
System.out.println(students.stream().noneMatch(student -> student.getAge() < 10));
//獲取所有學生的成績由大到小排序后的第一個學生信息
Optional<Student> first = students.stream().sorted((s1, s2)
-> Double.compare(s2.getScore(), s1.getScore())).findFirst();
first.ifPresent(System.out::println);
//通過并行流獲取成績小于60的任意一個學生的信息
Optional<Student> any = students.parallelStream().filter(student -> student.getScore() < 60).findAny();
any.ifPresent(System.out::println);
//獲取所有學生的人數(shù)
System.out.println(students.stream().count());
//獲取成績最高的學生的信息
Optional<Student> max = students.stream().max(Comparator.comparingDouble(Student::getScore));
max.ifPresent(System.out::println);
//獲取成績最低的學生的成績
Optional<Double> min = students.stream().map(Student::getScore).min(Double::compareTo);
min.ifPresent(System.out::println);
}
3.2.2歸約
歸約(reduce)可以將流中元素通過一個計算函數(shù)計算后,得到一個值
Stream源碼中有兩個reduce方法,一個是不可能為空,返回泛型類型T;一個是結(jié)果可能為空,返回Optional<T>,避免空指針。源碼如下:
1.結(jié)果不可能為空的情況:
T reduce(T identity, BinaryOperator<T> accumulator);
identity指從哪開始計算,accumulator累加器是一個BinaryOperator的函數(shù)式接口,源碼如下:
@FunctionalInterface
public interface BinaryOperator<T> extends BiFunction<T,T,T> {}
BinaryOperator繼承于BiFunction,只是它的兩個參數(shù)類型和返回值類型都是T類型。
栗子:
@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通過靜態(tài)方法引用調(diào)用Integer類中的sum方法求和,sum方法傳入兩個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)換為其他形式,接收一個Collector接口的實現(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();
}
不過我們一般不直接對Collector接口進行操作,我們常用的是Collectors工具類,Collectors中定義了一個內(nèi)部類,這個內(nèi)部類實現(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;
...
}
這個工具類中提供了一些靜態(tài)方法供我們使用,比如我們常用的toList方法,toSet方法,將流轉(zhuǎn)換為集合。
栗子:
@Test
public void test14(){
//將所有學生的姓名提取出來收集到一個集合中
List<String> list = students.stream().map(Student::getName).collect(Collectors.toList());
list.forEach(System.out::println);
}
如果Collectors中沒有我們想要將流轉(zhuǎn)換成的類型,還可以使用Collectors提供的toCollection方法:
@Test
public void test15(){
//將所有學生的姓名提取出來收集到一個集合中
Collection<String> collect = students.stream().map(Student::getName).collect(Collectors.toCollection(HashSet::new));
collect.forEach(System.out::println);
}
collect方法還有一個重載方法,只是這個重載方法用的不是很多,這里也分析一下哈,感興趣的朋友可以看下哈,不敢興趣,跳過即可。
<R> R collect(Supplier<R> supplier,
BiConsumer<R, ? super T> accumulator,
BiConsumer<R, R> combiner);
supplier,提供者函數(shù),是Supplier函數(shù)式接口類型,不傳入任何參數(shù),有一個返回值,這里是指新結(jié)果的容器,也就是collect方法要返回的結(jié)果。
accumulator累加器函數(shù),是BiConsumer消費式函數(shù)式接口類型,傳入兩個參數(shù),不返回任何值。這里用于對流中的每一個元素進行遍歷,將遍歷到的每個元素合并到結(jié)果中。
combiner,組合器函數(shù),也是BiConsumer類型,與上面所不同的是,這個BiConsumer的兩個參數(shù)都是R類型,必須與累加器函數(shù)兼容,也就是要與累加器函數(shù)中第一個參數(shù)代表的的容器類型相同。
舉個栗子:
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);
上述代碼等價于下面使用方法引用代碼:
Stream<String> stream = Stream.of("beijing", "shanghai", "tianjin");
List<String> list = stream.collect(ArrayList::new, ArrayList::add, ArrayList::addAll);
list.forEach(System.out::println);
上面栗子我們是要把一個stream流轉(zhuǎn)化為一個集合容器,并將stream流流中原來的三個元素,biejing,shanghai,tianjin,轉(zhuǎn)變?yōu)樾碌募先萜鞯脑亍?/p>
轉(zhuǎn)換過程(collect三個參數(shù)具體運作流程):
首先我們先需要用supplier提供者函數(shù)創(chuàng)建一個容器,來裝流中的三個元素,最后返回的結(jié)果也是這個容器。
接下來我們就要用accumulator這個累加器函數(shù)來來遍歷stream流中的每個元素,并把每次遍歷到的元素都添加到一個集合中。這個集合相當于一個中間集合,并不是我們上面用supplier提供者函數(shù)創(chuàng)建的要作為結(jié)果返回的集合容器。
最后我們用combiner組合器函數(shù)把上面中間集合里的元素一次性地都添加到我們要返回結(jié)果的集合容器中,也就是將theList中的元素都添加到theList1中,theList1就相當于supplier提供者函數(shù)創(chuàng)建的要作為結(jié)果返回的集合容器,theList2就相當于上面的theList.
下面我們來驗證一下以上第二步的分析是否和執(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));
}
}

我們的流中有三個元素,所以在第二步中經(jīng)過了三次遍歷,每次遍歷到一個元素,并將每次遍歷到的元素添加到theList集合中,三次遍歷后theList集合的元素為:[beijing, shanghei, tianjin]。
怎么樣?是不是不太好理解,這個重載方法確實比較難容易理解,不過多琢磨琢磨,結(jié)合著源碼和示例代碼練習一下還是會理解的,我學習這個方法也琢磨了挺長時間。
再舉一例吧,jdk官方文檔的一個栗子:
String concat = stream.collect(StringBuilder::new, StringBuilder::append,
StringBuilder::append)
.toString();
我們稍微修改一下,把方法引用改成用lambada表達式更容易理解。
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集合的底層實現(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);
}
看到這里我們應該恍然大悟了吧,原來Collectors.toList()方法用的就是我們上面分析的帶三個參數(shù)的collect的重載方法實現(xiàn)的,二者的本質(zhì)是一樣的。
除了以上的toList,toConllection,toSet方法以外,Collectors工具類還為我們提供了一些其他的實用的方法,比如求總數(shù),平均數(shù),最大值,最小值,分組,分區(qū)等,下面就介紹一些我們比較常用的方法。
@Test
public void test16(){
//獲取學生人數(shù)
Long count = students.stream().collect(Collectors.counting());
System.out.println(count);
//求學生成績的平均分
Double averageScore = students.stream().collect(Collectors.averagingDouble(Student::getScore));
System.out.println(averageScore);
//求所有學生總成績
double sumScore = students.stream().collect(Collectors.summingDouble(Student::getScore));
System.out.println(sumScore);
//獲取成績最高的學生信息
Optional<Student> max = students.stream().collect(Collectors.maxBy((s1, s2)
-> Double.compare(s1.getScore(), s2.getScore())));
max.ifPresent(System.out::println);
//獲取成績最底的學生的成績
Optional<Double> min = students.stream().map(Student::getScore).collect(Collectors.minBy(Double::compareTo));
min.ifPresent(System.out::println);
//按照level將所有學生分組
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ū),成績大于等于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流是不是有點像寫sql語句,其實jdk8中的stream流挺類似于sql語句的,都是一種描述性語言,讓開發(fā)者只需要寫出想要的結(jié)果對應的語句,而不用關心這些語句的底層是怎么實現(xiàn)和執(zhí)行的。
同時Collectors還有一類統(tǒng)計方法,用于統(tǒng)計個數(shù),總數(shù),最大值,最小值以及求平均數(shù)等,這類方法有summarizingInt,summarizingLong,summarizingDouble,分別對應于數(shù)值類型的整型,長整型,double類型。以summarizingDouble為例介紹下如何使用。
@Test
public void test17(){
//統(tǒng)計
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());
}
隨后一個方法,連接方法:
@Test
public void test18(){
//連接
String collect = students.stream().map(Student::getName).collect(Collectors.joining(","));
System.out.println(collect);
}
4.注意
提一點使用流的注意事項,就是我們在使用流時要注意流是不能被重復使用或消費的。不然會報下面的異常。
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,因為和上面使用的是同一個流對象,而一個流的對象不能被重復使用
System.out.println(stream.distinct());//和上面用的同一個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é)
終于寫完了,是不是很啰嗦,太長了,為了保持內(nèi)容的連續(xù)性就沒有拆分成幾篇文章,腦瓜疼,難免有介紹的不到位的地方,也可能有錯別字,雖然我已經(jīng)檢查了好幾遍,大家就湊活看吧,能對大家的學習起到點幫助作用就可以了,同時我自己也可以回過頭來復習已經(jīng)忘記的知識。如果大家發(fā)現(xiàn)有不對的地方,歡迎留言指正,錯別字也可以。輸出不易,大家給點個贊也行(皮一下哈),同時也歡迎大家關注微信公眾號,后期會有更多的輸出,共同學習,不斷進步,持續(xù)輸出。
