流是Java API的新成員,以聲明的方式處理數據合集。就現在來說,你可以把它們看成遍歷數據集的高級迭代器。可以透明地並行處理,你無需寫任何多線程代碼了。
一、java7與java8中實現方式對比
我們首先舉個例子,看一下在java7和java8當中,引入Stream Api後,究竟發生了哪些變化?
實現一個從菜單篩選低熱量(小於500)菜餚名稱並從小到大排序的功能:
1.1 java7實現
import lombok.Data;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Comparator;
import java.util.List;
/**
* @description: 實現一個從菜單篩選低熱量(小於500)菜餚名稱並從小到大排序的功能
* @author:weirx
* @date:2021/10/20 15:00
* @version:3.0
*/
public class ChoiceCalorieByJava7 {
@Data
static class Dish {
private String name;
private Integer calorie;
public Dish(String name, Integer calorie) {
this.name = name;
this.calorie = calorie;
}
}
public static void main(String[] args) {
List<Dish> dishes = Arrays.asList(
new Dish("燒烤", 1000),
new Dish("沙拉", 100),
new Dish("漢堡", 1200),
new Dish("火鍋", 800),
new Dish("粥", 300)
);
List<Dish> lowerCalorieDishes = new ArrayList<>();
// 篩選小於500卡路里
for (Dish dish : dishes) {
if (dish.getCalorie() < 500) {
lowerCalorieDishes.add(dish);
}
}
// 排序
lowerCalorieDishes.sort(new Comparator<Dish>() {
@Override
public int compare(Dish o1, Dish o2) {
return o1.getCalorie().compareTo(o2.getCalorie());
}
});
List<String> dishNames = new ArrayList<>();
// 獲取菜餚名稱
for (Dish dish : lowerCalorieDishes) {
dishNames.add(dish.getName());
}
// 打印結果 [沙拉, 粥]
System.out.println(dishNames);
}
}
1.2 java8實現
import lombok.Data;
import java.util.Arrays;
import java.util.Comparator;
import java.util.List;
import java.util.stream.Collectors;
/**
* @description: 實現一個從菜單篩選低熱量(小於500)菜餚名稱並從小到大排序的功能
* @author:weirx
* @date:2021/10/20 15:00
* @version:3.0
*/
public class ChoiceCalorieByJava8 {
@Data
static class Dish {
private String name;
private Integer calorie;
public Dish(String name, Integer calorie) {
this.name = name;
this.calorie = calorie;
}
}
public static void main(String[] args) {
List<Dish> dishes = Arrays.asList(
new Dish("燒烤", 1000),
new Dish("沙拉", 100),
new Dish("漢堡", 1200),
new Dish("火鍋", 800),
new Dish("粥", 300)
);
List<String> dishNames = dishes.stream()
.filter(dish -> dish.getCalorie() < 500) //篩選卡路里小於500
.sorted(Comparator.comparing(Dish::getCalorie)) //排序
.map(Dish::getName) //獲取名稱
.collect(Collectors.toList()); //轉成list
// 打印結果 [沙拉, 粥]
System.out.println(dishNames);
}
}
相比之下在java7中繁雜的業務代碼只需要一條流式的代碼就完成了,並且將其中的stream()替換成parallelStream()就可以利用多核去執行這行代碼。
1.3 總結
下面通過上面的例子,簡單總結下使用Stream API的好處:
1)聲明性:更簡潔,更易讀。體現事物本身,而非代碼過程。
2)可複合:更靈活,減少大量的重複代碼。
3)可並行:更好的性能。
二、如何理解流?
簡短的定義就是:從支持數據處理操作的源生成的元素序列。
下面簡單介紹一下這句話的含義:
1)元素序列:像集合一樣,流也提供一個接口,可以訪問特定元素的一組有序值。集合(ArrayList、LinkedList等)關注數據,而流關注的是計算(filter 、 sorted 和 map等)。
2)源:流會使用一個提供數據的源,如集合、數組或輸入/輸出資源。 請注意,從有序集合生成流時會保留原有的順序。由列表生成的流,其元素順序與列表一致。
3)數據處理操作:流的數據處理功能支持類似於數據庫的操作,以及函數式編程語言中的常用操作,如 filter 、 map 、 reduce 、 find 、 match 、 sort 等。流操作可以順序執行,也可並行執行。
除此之外還有以下兩個特點:
4)流水線:多流操作本身會返回一個流,這樣多個操作就可以鏈接起來,形成一個大的流水線。流水線的操作可以看作對數據源進行數據庫式查詢。
5)內部迭代:與使用迭代器顯式迭代的集合不同,流的迭代操作是在背後進行的。
結合前面的例子簡單說明下:
List<String> dishNames = dishes.stream()
.filter(dish -> dish.getCalorie() < 500) //篩選卡路里小於500
.sorted(Comparator.comparing(Dish::getCalorie)) //排序
.map(Dish::getName) //獲取名稱
.collect(Collectors.toList()); //轉成list
其中dishNames就是源,它給流提供一個元素序列,接下來,對流應用一系列數據處理操作:filter 、 sorted 和 map等。最後, collect 操作開始處理流水線,並返回結果(它和別的操作不一樣,因爲它返回的不是流,在這裏是一個 List。
三、流和集合有哪些不同?
1)集合可以多次遍歷,而流只能被遍歷一次
public static void main(String[] args) {
List<Integer> list = new ArrayList<>();
Stream<Integer> stream = list.stream();
stream.forEach(System.out::print);
stream.forEach(System.out::print);
}
如上操作會報出以下異常:
Exception in thread "main" java.lang.IllegalStateException: stream has already been operated upon or closed
at java.util.stream.AbstractPipeline.sourceStageSpliterator(AbstractPipeline.java:279)
at java.util.stream.ReferencePipeline$Head.forEach(ReferencePipeline.java:580)
at com.cloud.bssp.java8.stream.CollectionAndStream.main(CollectionAndStream.java:19)
2)遍歷數據的方式不同
使用 Collection 接口需要用戶去做迭代(比如用 foreach ),這稱爲外部迭代。 相反,Streams庫使用內部迭代
相比於外部迭代,內部迭代的優勢在於:
內部迭代時,項目可以透明地並行處理,或者用更優化的順序進行處理。不需要像外部迭代手動去處理並行問題。
四、流的操作
Stream API定義了很多的操作,這些操作可以分爲兩大類:
1)filter、map、limit等可以連成一條流水線。這類操作也可以簡稱爲中間操作。
諸如 filter 或 sorted 等中間操作會返回另一個流。這讓多個操作可以連接起來形成一個查詢。重要的是,除非流水線上觸發一個終端操作,否則中間操作不會執行任何處理。這是因爲中間操作一般都可以合併起來,在終端操作時一次性全部處理。
2)collect觸發流水線執行並關閉它。這類操作被稱爲終端操作。
終端操作會從流的流水線生成結果。其結果是任何不是流的值,比如 List 、 Integer ,甚至 void 。
總而言之,流的使用一般包括三件事:
1)一個數據源(如集合)來執行一個查詢;
2)一箇中間操作鏈,形成一條流的流水線;
3)一個終端操作,執行流水線,並能生成結果