流是什麼
流是Java8 API的新功能,它允許使用聲明性方式處理集合。可以将其看成周遊資料集的進階疊代器。此外,流還可以透明地并行處理。
例如下面這行代碼:
// 利用多核架構并行處理
menus.parallelStream()
// 選出400卡路裡以下的菜
.filter(dish -> dish.getCalories() < 400)
// 按照卡路裡排序
.sorted(Comparator.comparing(Dish::getCalories))
// 提取菜名
.map(Dish::getName)
// 隻選擇頭三個
.limit(3L)
// 将結果儲存在List集合裡
.collect(Collectors.toList())
// 列印結果
.forEach(System.out::println);
流的優點:
- 代碼是以聲明性方式寫的:說明想要完成什麼而非說明如何實作該操作。
- 可以将多個基礎操作連結起來,以此來表達複雜的資料處理流水線,同時保持代碼清晰可讀。
menus=>start: menus
List=>end: List
filter=>subroutine: filter
sorted=>subroutine: sorted
map=>subroutine: map
menus(right)->filter(right)->sorted(right)->map(right)->List
流的定義
簡單來說,流就是從支援資料處理操作的源生成的元素序列。
- 元素序列:就像集合一樣,流提供一個接口,通過該接口可以通路特定元素類型的一組有序值。因為集合是資料結構,是以它的主要目的是以特定的時間/空間複雜度存儲和通路元素。但流的目的在于表達計算。
- 源:流會使用一個提供資料的源,這個源可以是集合、數組或輸入資源。若從有序集合生成流時會保留原有的順序。
- 資料處理操作:流的資料處理功能支援類似資料庫的操作,以及函數式程式設計語言中的常用操作,如
、filter
map
reduce
find
match
等。流操作可以順序執行,也可以并行執行。sort
流的特點
- 流水線——很多流操作本身會傳回一個流,這樣多個操作就可以連結起來,形成一個大的流水線。流水線的操作可以看作對資料源進行資料庫式的查詢。
- 内部疊代——與使用疊代器顯示疊代的集合不同,流的疊代操作是在背後進行的。
流的内部優化
我們看一段代碼:
menu.stream()
// 篩選出不是蔬菜的食物
.filter(dish -> {
System.out.println(dish.toString());
System.out.println("------------this is filter--------------");
return !dish.getVegetarian();
})
// 将對象轉換為string類型
.map(dish->{
System.out.println(dish.toString());
System.out.println("------------this is map--------------");
return dish.toString();
})
// 隻擷取四個
.limit(4L)
.collect(Collectors.toSet())
.forEach(System.out::println);
列印在控制台的結果讓人驚歎——
Dish(name=pork, vegetarian=false, calories=800, type=MEAT)
------------this is filter--------------
Dish(name=pork, vegetarian=false, calories=800, type=MEAT)
------------this is map--------------
Dish(name=beef, vegetarian=false, calories=700, type=MEAT)
------------this is filter--------------
Dish(name=beef, vegetarian=false, calories=700, type=MEAT)
------------this is map--------------
Dish(name=chick, vegetarian=false, calories=400, type=MEAT)
------------this is filter--------------
Dish(name=chick, vegetarian=false, calories=400, type=MEAT)
------------this is map--------------
Dish(name=french fries, vegetarian=true, calories=530, type=OTHER)
------------this is filter--------------
Dish(name=rice, vegetarian=true, calories=350, type=OTHER)
------------this is filter--------------
Dish(name=season fruit, vegetarian=true, calories=120, type=OTHER)
------------this is filter--------------
Dish(name=pizza, vegetarian=true, calories=550, type=OTHER)
------------this is filter--------------
Dish(name=prawns, vegetarian=false, calories=300, type=FISH)
------------this is filter--------------
Dish(name=prawns, vegetarian=false, calories=300, type=FISH)
------------this is map--------------
Dish(name=chick, vegetarian=false, calories=400, type=MEAT)
Dish(name=prawns, vegetarian=false, calories=300, type=FISH)
Dish(name=beef, vegetarian=false, calories=700, type=MEAT)
Dish(name=pork, vegetarian=false, calories=800, type=MEAT)
這些優化用到了流的延遲性質。盡管和
filter
是兩個獨立的操作,但是它們合并到同一次周遊中。
map
流的使用
-
方法:該方法會接受一個filter
類型結果的函數,并傳回一個符合條件的流。boolean
例如:
Stream.of(1, 2, 3, 4, 5, 6, 7, 6, 5, 4, 3, 2, 1)
// 篩選出偶數
.filter(num -> num % 2 == 0)
.forEach(System.out::println);
// result: 2, 4, 6, 6, 4, 2
-
方法:該方法會去除流中重複的元素(根據流所生成元素的distinct
hashCode
方法實作)。equals
Stream.of(1, 2, 3, 4, 5, 6, 7, 6, 5, 4, 3, 2, 1)
// 去除重複的元素
.distinct()
.forEach(System.out::println);
// result: 1, 2, 3, 4, 5, 6, 7
-
方法:該方法會傳回一個不超過給定長度的流。limit
Stream.of(1, 2, 3, 4, 5, 6, 7, 6, 5, 4, 3, 2, 1)
// 擷取前5個對象
.limit(5L)
.forEach(System.out::println);
// result: 1, 2, 3, 4, 5
-
方法:該方法會傳回一個扔掉了前n個元素的流。skip
Stream.of(1, 2, 3, 4, 5, 6, 7, 6, 5, 4, 3, 2, 1)
// 扔掉前5個對象
.skip(5L)
.forEach(System.out::println);
// result: 6, 7, 6, 5, 4, 3, 2, 1
-
方法:該方法會對每個元素進行函數運算,并将其映射為一個新的元素(傳回函數運算結果)。map
例如(對每個元素進行函數運算):
Stream.of(1, 2, 3, 4, 5, 6, 7, 6, 5, 4, 3, 2, 1)
// 對每個元素進行統一的操作
.map(num -> ++num)
.forEach(System.out::println);
// result: 2, 3, 4, 5, 6, 7, 8, 7, 6, 5, 4, 3, 2
例如(對集合内某一進制素進行提取:
List<Dish> menu = Arrays.asList(
new Dish("pork", false, 800, Type.MEAT),
new Dish("beef", false, 700, Type.MEAT),
new Dish("chick", false, 400, Type.MEAT),
new Dish("french fries", true, 530, Type.OTHER),
new Dish("rice", true, 350, Type.OTHER),
new Dish("season fruit", true, 120, Type.OTHER)
);
// 提取每道菜的名字
List<String> dishNames = menu.stream()
.map(Dish::getName)
.collect(Collectors.toList());
dishNames.forEach(System.out::println);
//result: pork, beef, chick, french fries, rice, season fruit
-
方法:該方法會将一個流中的每個元素轉換為流,一個元素對應一個流,然後把所有的流再連接配接起來成為一個流。flatMap
List<List<String>> lists = Arrays.asList(
Arrays.asList("1", "2", "3"),
Arrays.asList("4", "5", "6"),
Arrays.asList("7", "8", "9")
);
List<String> strings = lists.stream()
// 将每個集合都轉換為數組
.map(List::toArray)
// 将每個數組内的元素轉換為流
.flatMap(Arrays::stream)
.map(Object::toString)
.collect(Collectors.toList());
strings.forEach(System.out::println);
// result: "1", "2", "3", "4", "5", "6", "7", "8", "9"
-
方法:該方法會判斷流中是否有至少一個元素能比對給定的判斷條件。anyMatch
// 判斷菜單裡是否有蔬菜
boolean isVegetarian = menu.stream().anyMatch(Dish::getVegetarian);
-
方法:該方法會判斷流中是否任何元素都能比對給定的判斷條件。allMatch
// 判斷菜單裡是否全部都是蔬菜
boolean isVegetarian = menu.stream().allMatch(Dish::getVegetarian);
-
方法:該方法會判斷流中是否任何元素都不比對給定的判斷條件。noneMatch
// 判斷菜單裡是否沒有蔬菜
boolean isVegetarian = menu.stream().noneMatch(Dish::getVegetarian);
-
方法:該方法會傳回流中的任意元素。findAny
Optional<Dish> optionalDish = menu.stream()
.filter(Dish::getVegetarian)
.findAny();
// 是否存在蔬菜類食物
boolean isVegetarian = optionalDish.isPresent();
if (isVegetarian){
// 如果存在則輸出該食物資訊
System.out.println(optionalDish.get().toString());
return;
}
System.out.println("not vegetarian in menu");
}
-
方法:該方法會傳回流中的地一個元素。findFirst
Optional<Integer> optionalInteger = Stream.of(1, 2, 3, 4, 5, 6)
.filter(num -> num % 3 == 0)
.findFirst();
boolean isHave = optionalInteger.isPresent();
if (isHave) {
System.out.println(optionalInteger.get().toString());
return;
}
System.out.println("not num");
}
-
方法:該方法有接收辨別符符合reduce
的表達式,将兩個元素結合起來傳回一個新的值。BinaryOperator<T>
Stream<Integer> integerStream = Stream.of(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
// 求和
Integer sum = integerStream.reduce(Integer::sum).get();
// 最大值
Integer max = integerStream.reduce(Integer::max).get();
// 最小值
Integer min = integerStream.reduce(Integer::min).get();
......