Stream-快速入門Stream程式設計
一、什麼是流
Stream 不是集合元素,它不是資料結構並不儲存資料,它是有關演算法和計算的,它更像一個高階版本的 Iterator。原始版本的 Iterator,使用者只能顯式地一個一個遍歷元素並對其執行某些操作;高階版本的 Stream,使用者只要給出需要對其包含的元素執行什麼操作,比如 “過濾掉長度大於 10 的字串”、“獲取每個字串的首字母”等,Stream 會隱式地在內部進行遍歷,做出相應的資料轉換。
Stream 就如同一個迭代器(Iterator),單向,不可往復,資料只能遍歷一次,遍歷過一次後即用盡了,就好比流水從面前流過,一去不復返。
流看作在時間中分佈的一組值。相反,集合則是空間(這裡就是計算機記憶體)中分佈的一組值,在一個時間點上全體存在——你可以使用迭代器來訪問for-each迴圈中的內部成員。
Java 7 之前的處理:
List<Dish> lowCaloricDishes = new ArrayList<>(); for(Dish d: menu){ if(d.getCalories() < 400){ lowCaloricDishes.add(d); } } Collections.sort(lowCaloricDishes, new Comparator<Dish>() { public int compare(Dish d1, Dish d2){ return Integer.compare(d1.getCalories(), d2.getCalories()); } }); List<String> lowCaloricDishesName = new ArrayList<>(); for(Dish d: lowCaloricDishes){ lowCaloricDishesName.add(d.getName()); }
Java 8 流處理:
import static java.util.Comparator.comparing;
import static java.util.stream.Collectors.toList;
List<String> lowCaloricDishesName =
menu.stream()
.filter(d -> d.getCalories() < 400)
.sorted(comparing(Dish::getCalories))
.map(Dish::getName)
.collect(toList());
Stream Api的特性:
- 宣告性--更簡潔,更易讀
- 可複合--更靈活
- 可並行--效能更好
- 只能遍歷一遍
二、使用流
簡單說,對 Stream 的使用就是實現一個 filter-map-reduce 過程,產生一個最終結果,或者導致一個副作用(side effect)。例如:
List<String> name = menu.stream()
.filter(dish -> dish.getCalories() > 300)
.map(Dish::getName)
.limit(3)
.collect(Collectors.toList());
- filter:從流中排除某些元素
- map:將元素轉換為其他形式或提取訊息
- limit:截斷流
- collect:將流轉換為其他形式
接下來,我們將開始學習Stream中的Api 使用技巧,接下來將會使用到實體類 -Dish,以及連結串列:
實體類:Dish
public class Dish {
private String name;
private boolean vegetarian;
private int calories;
private Type type;
public enum Type {MEAT, FISH, OTHER}
public Dish(String name, boolean vegetarian, int calories, Type type) {
this.name = name;
this.vegetarian = vegetarian;
this.calories = calories;
this.type = type;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public boolean isVegetarian() {
return vegetarian;
}
public void setVegetarian(boolean vegetarian) {
this.vegetarian = vegetarian;
}
public int getCalories() {
return calories;
}
public void setCalories(int calories) {
this.calories = calories;
}
public Type getType() {
return type;
}
public void setType(Type type) {
this.type = type;
}
}
使用連結串列:
Arrays.asList(
new Dish("pork", false, 800, Dish.Type.MEAT),
new Dish("beef", false, 700, Dish.Type.MEAT),
new Dish("chicken", false, 400, Dish.Type.MEAT),
new Dish("french fries", true, 530, Dish.Type.OTHER),
new Dish("rice", true, 350, Dish.Type.OTHER),
new Dish("season fruit", true, 120, Dish.Type.OTHER),
new Dish("pizza", true, 550, Dish.Type.OTHER),
new Dish("prawns", false, 300, Dish.Type.FISH),
new Dish("salmon", false, 450, Dish.Type.FISH));
1、篩選 Filter
1.1 謂詞篩選 filter
該操作會接受一個謂詞(一個返回boolean的函式)作為引數,並返回一個包括所有符合謂詞的元素的流。
示例程式碼:
List<Dish> vegatarianMenu = menu.stream()
.filter(Dish::isVegetarian)
.collect(Collectors.toList());
1.2 去重篩選 distinct()
流還支援一個叫作distinct的方法,它會返回一個元素各異(根據流所生成元素的 hashCode和equals方法實現)的流
示例程式碼:
List<Integer> numbers = Arrays.asList(1, 2, 1, 3, 3, 2, 4);
numbers.stream()
.filter(integer -> integer % 2 == 0)
.distinct()
.forEach(System.out::println);
輸出結果為:2,4(去重一個 2)
1.3 截短流 limit(n)
流支援limit(n)方法,該方法會返回一個不超過給定長度的流。所需的長度作為引數傳遞 給limit
List<Dish> vegatarianMenu = menu.stream()
.filter(Dish::isVegetarian)
.limit(3)
.collect(Collectors.toList());
1.4 跳過元素 skip(n)
流還支援skip(n)方法,返回一個扔掉了前n個元素的流。如果流中元素不足n個,則返回一個空流
List<Dish> vegatarianMenu = menu.stream()
.filter(Dish::isVegetarian)
.skip(2)
.collect(Collectors.toList());
2、對映 Map
流支援map方法,它會接受一個函式作為引數。這個函式會被應用到每個元素上,並將其對映成一個新的元素(使用對映一詞,是因為它和轉換類似,但其中的細微差別在於它是“建立一個新版本”而不是去“修改”
2.1 對流中的每一個元素應用
List<String> dishNames = menu.stream()
.map(Dish::getName)
.collect(Collectors.toList());
2.2 流的扁平化
對於一張單詞表, 如何返回一張列表, 列出裡面各不相同的字元呢? 例如, 給定單詞列表["Hello","World"],你想要返回列表["H","e","l", "o","W","r","d"]
List<String> uniqueCharacters = words.stream()
.map(w -> w.split(""))
.flatMap(Arrays::stream)
.distinct()
.collect(Collectors.toList());
3、匹配查詢 Match Find
3.1 AnyMatch
anyMatch方法可以回答“流中是否有一個元素能匹配給定的謂詞”。返回結果為boolean資料型別,如果流中出現匹配項,返回True。
if (list.stream().anyMatch(Dish::isVegetarian)){
System.out.printf("The menu is (somewhat) vegetarian friendly!!");
}
3.2 AllMatch
AllMatch 用法和AnyMatch 相似,方法返回結果為“流中所有結果都符合判斷規則”。
boolean flag = list.stream().allMatch(dish -> dish.getCalories() <1000);
3.3 NoneMatch
NoneMatch 與 AllMatch 恰恰相反,返回結果為“流中所有結果都不符合”。
boolean flag = list.stream().allMatch(dish -> dish.getCalories() <1000);
3.4 findAny
findAny方法將返回當前流中的任意元素。它可以與其他流操作結合使用。方法返回結果為 Optional<T>。
list.stream()
.filter(Dish::isVegetarian)
.findAny()
.ifPresent(d -> System.out.println(d.getName()));
3.5 findFirst
有些流有一個出現順序(encounterorder)來指定流中專案出現的邏輯順序(比如由List或排序好的資料列生成的流)。對於這種流,你可能想要找到第一個元素。為此有一個finFirst 方法,它的工作方式類似於findany。
someNumbers.stream()
.map(x -> x * x)
.filter(x -> x % 3 == 0)
.findFirst()
.ifPresent(System.out::println);
3.6 何時使用findFirst和findAny
你可能會想,為什麼會同時有findFirst和findAny呢?答案是並行。找到第一個元素 在並行上限制更多。如果你不關心返回的元素是哪個,請使用findAny,因為它在使用並行流 時限制較少。
4.歸約 Reduce
需要將流中所有元素反覆結合起來,得到一個值,比如一個Integer。這樣的查詢可以被歸類為約操作 (將流歸約成一個值)。
元素求和
//Type 1
int result = list.stream()
.reduce(0, Integer::sum);
System.out.println(result);
//Type 2
list.stream()
.reduce(Integer::sum)
.ifPresent(System.out::println);
最大值和最小值
//Max
list.stream()
.reduce(Integer::max)
.ifPresent(System.out::println);
//Min
list.stream()
.reduce(Integer::min)
.ifPresent(System.out::println);
三、總結
github 地址:https://github.com/jaycekon/StreamDemo
- 流是“從支援資料處理操作的源生成的一系列元素”
- 流利用內部迭代:迭代通過filter、map、sorted等操作被抽象掉了。
- 流操作有兩類:中間操作和終端操作。
- filter和map等中間操作會返回一個流,並可以連結在一起。可以用它們來設定一條流 水線,但並不會生成任何結果。
- forEach和count等終端操作會返回一個非流的值,並處理流水線以返回結果。
- 流中的元素是按需計算的。