注意:Stream和IO流(InputStream/OutputStream)沒有任何關系,請暫時忘記對傳統IO流的固有印象
傳統集合的多步遍歷代碼
幾乎所有的集合(如Collection介面或Map介面等)都支持直接或間接的遍歷操作,而當我們需要對集合中的元素進行操作的時候,除了必需的添加、洗掉、獲取外,最典型的就是集合遍歷,例如:
public class Demo10ForEach {
public static void main(String[] args) {
List<String> list = new ArrayList<>();
list.add("張無忌");
list.add("周芷若");
list.add("趙敏");
list.add("張強");
list.add("張三豐");
for (String name : list) {
System.out.println(name);
}
}
}
這是一段非常簡單的集合遍歷操作:對集合中的每一個字串都進行列印輸出操作,
回圈遍歷的弊端
Java 8的Lambda讓我們可以更加專注于做什么(What),而不是怎么做(How),這點此前已經結合內部類進行了對比說明,現在,我們仔細體會一下上例代碼,可以發現:
- for回圈的語法就是“怎么做”
- for回圈的回圈體才是“做什么”
為什么使用回圈?因為要進行遍歷,但回圈是遍歷的唯一方式嗎?遍歷是指每一個元素逐一進行處理,而并不是從第一個到最后一個順次處理的回圈,前者是目的,后者是方式,
試想一下,如果希望對集合中的元素進行篩選過濾:
- 將集合A根據條件一過濾為子集B;
- 然后再根據條件二過濾為子集C,
那怎么辦?在Java 8之前的做法可能為:
這段代碼中含有三個回圈,每一個作用不同:
- 首先篩選所有姓張的人;
- 然后篩選名字有三個字的人;
- 最后進行對結果進行列印輸出,
public class Demo11NormalFilter {
public static void main(String[] args) {
List<String> list = new ArrayList<>();
list.add("張無忌");
list.add("周芷若");
list.add("趙敏");
list.add("張強");
list.add("張三豐");
List<String> zhangList = new ArrayList<>();
for (String name : list) {
if (name.startsWith("張")) {
zhangList.add(name);
}
}
List<String> shortList = new ArrayList<>();
for (String name : zhangList) {
if (name.length() == 3) {
shortList.add(name);
}
}
for (String name : shortList) {
System.out.println(name);
}
}
}
每當我們需要對集合中的元素進行操作的時候,總是需要進行回圈、回圈、再回圈,這是理所當然的么?不是,回圈是做事情的方式,而不是目的,另一方面,使用線性回圈就意味著只能遍歷一次,如果希望再次遍歷,只能再使用另一個回圈從頭開始,
那,Lambda的衍生物Stream能給我們帶來怎樣更加優雅的寫法呢?
Stream的更優寫法
下面來看一下借助Java 8的Stream API,什么才叫優雅:
public class Demo12StreamFilter {
public static void main(String[] args) {
List<String> list = new ArrayList<>();
list.add("張無忌");
list.add("周芷若");
list.add("趙敏");
list.add("張強");
list.add("張三豐");
list.stream()
.filter(s -> s.startsWith("張"))
.filter(s -> s.length() == 3)
.forEach(s -> System.out.println(s));
}
}
直接閱讀代碼的字面意思即可完美展示無關邏輯方式的語意:獲取流、過濾姓張、過濾長度為3、逐一列印,代碼中并沒有體現使用線性回圈或是其他任何演算法進行遍歷,我們真正要做的事情內容被更好地體現在代碼中,
獲取流方式
java.util.stream.Stream<T>是Java 8新加入的最常用的流介面,(這并不是一個函式式介面,)
獲取一個流非常簡單,有以下幾種常用的方式:
- 所有的
Collection集合都可以通過stream默認方法獲取流; Stream介面的靜態方法of可以獲取陣列對應的流,
方式1 : 根據Collection獲取流
首先,java.util.Collection介面中加入了default方法stream用來獲取流,所以其所有實作類均可獲取流,
import java.util.*;
import java.util.stream.Stream;
/*
獲取Stream流的方式
1.Collection中 方法
Stream stream()
2.Stream介面 中靜態方法
of(T...t) 向Stream中添加多個資料
*/
public class Demo13GetStream {
public static void main(String[] args) {
List<String> list = new ArrayList<>();
// ...
Stream<String> stream1 = list.stream();
Set<String> set = new HashSet<>();
// ...
Stream<String> stream2 = set.stream();
}
}
方式2: 根據陣列獲取流
如果使用的不是集合或映射而是陣列,由于陣列物件不可能添加默認方法,所以Stream介面中提供了靜態方法of,使用很簡單:
import java.util.stream.Stream;
public class Demo14GetStream {
public static void main(String[] args) {
String[] array = { "張無忌", "張翠山", "張三豐", "張一元" };
Stream<String> stream = Stream.of(array);
}
}
備注:
of方法的引數其實是一個可變引數,所以支持陣列,
常用方法
流模型的操作很豐富,這里介紹一些常用的API,這些方法可以被分成兩種:
- 終結方法:回傳值型別不再是
Stream介面自身型別的方法,因此不再支持類似StringBuilder那樣的鏈式呼叫,本小節中,終結方法包括count和forEach方法, - 非終結方法:回傳值型別仍然是
Stream介面自身型別的方法,因此支持鏈式呼叫,(除了終結方法外,其余方法均為非終結方法,)
備注:本小節之外的更多方法,請自行參考API檔案,
forEach : 逐一處理
雖然方法名字叫forEach,但是與for回圈中的“for-each”昵稱不同,該方法并不保證元素的逐一消費動作在流中是被有序執行的,
void forEach(Consumer<? super T> action);
該方法接收一個Consumer介面函式,會將每一個流元素交給該函式進行處理,例如:
import java.util.stream.Stream;
public class Demo15StreamForEach {
public static void main(String[] args) {
Stream<String> stream = Stream.of("大娃","二娃","三娃","四娃","五娃","六娃","七娃","爺爺","蛇精","蝎子精");
//Stream<String> stream = Stream.of("張無忌", "張三豐", "周芷若");
stream.forEach((String str)->{System.out.println(str);});
}
}
在這里,lambda運算式(String str)->{System.out.println(str);}就是一個Consumer函式式介面的示例,
filter:過濾
可以通過filter方法將一個流轉換成另一個子集流,方法宣告:
Stream<T> filter(Predicate<? super T> predicate);
該介面接收一個Predicate函式式介面引數(可以是一個Lambda)作為篩選條件,
基本使用
Stream流中的filter方法基本使用的代碼如:
public class Demo16StreamFilter {
public static void main(String[] args) {
Stream<String> original = Stream.of("張無忌", "張三豐", "周芷若");
Stream<String> result = original.filter((String s) -> {return s.startsWith("張");});
}
}
在這里通過Lambda運算式來指定了篩選的條件:必須姓張,
count:統計個數
正如舊集合Collection當中的size方法一樣,流提供count方法來數一數其中的元素個數:
long count();
該方法回傳一個long值代表元素個數(不再像舊集合那樣是int值),基本使用:
public class Demo17StreamCount {
public static void main(String[] args) {
Stream<String> original = Stream.of("張無忌", "張三豐", "周芷若");
Stream<String> result = original.filter(s -> s.startsWith("張"));
System.out.println(result.count()); // 2
}
}
limit:取用前幾個limit方法可以對流進行截取,只取用前n個,方法簽名:
Stream<T> limit(long maxSize):獲取Stream流物件中的前n個元素,回傳一個新的Stream流物件
引數是一個long型,如果集合當前長度大于引數則進行截取;否則不進行操作,
基本使用:
import java.util.stream.Stream;
public class Demo18StreamLimit {
public static void main(String[] args) {
Stream<String> original = Stream.of("張無忌", "張三豐", "周芷若");
Stream<String> result = original.limit(2);
System.out.println(result.count()); // 2
}
}
skip:跳過前幾個
如果希望跳過前幾個元素,可以使用skip方法獲取一個截取之后的新流:
Stream<T> skip(long n): 跳過Stream流物件中的前n個元素,回傳一個新的Stream流物件
如果流的當前長度大于n,則跳過前n個;否則將會得到一個長度為0的空流,
基本使用:
import java.util.stream.Stream;
public class Demo19StreamSkip {
public static void main(String[] args) {
Stream<String> original = Stream.of("張無忌", "張三豐", "周芷若");
Stream<String> result = original.skip(2);
System.out.println(result.count()); // 1
}
}
concat:組合
如果有兩個流,希望合并成為一個流,那么可以使用Stream介面的靜態方法concat:
static <T> Stream<T> concat(Stream<? extends T> a, Stream<? extends T> b): 把引數串列中的兩個Stream流物件a和b,合并成一個新的Stream流物件
備注:這是一個靜態方法,與
java.lang.String當中的concat方法是不同的,
該方法的基本使用代碼如:
import java.util.stream.Stream;
public class Demo20StreamConcat {
public static void main(String[] args) {
Stream<String> streamA = Stream.of("張無忌");
Stream<String> streamB = Stream.of("張翠山");
Stream<String> result = Stream.concat(streamA, streamB);
}
}
distinct:去重
如果需要去除重復資料,可以使用 distinct方法,方法簽名:
Stream<T> distinct()
基本使用:
public class Test {
public static void main(String[] args) {
Stream.of(22, 33, 22, 11, 33)
.distinct()
.forEach(s-> System.out.println(s));
}
}
map:映射
如果需要將流中的元素映射到另一個流中,可以使用 map方法,方法簽名:
<R> Stream<R> map(Function<? super T, ? extends R> mapper);
該介面需要一個 Function函式式介面引數,可以將當前流中的T型別資料轉換為另一種R型別的流,
基本使用:
public class Test {
public static void main(String[] args) {
Stream<String> original = Stream.of("11", "22", "33");
Stream<Integer> result = original.map(Integer::parseInt);
result.forEach(s -> System.out.println(s + 10));
}
}
練習
現在有兩個ArrayList集合存盤隊伍當中的多個成員姓名,要求使用傳統的for回圈(或增強for回圈)依次進行以下若干操作步驟:
- 第一個隊伍只要名字為3個字的成員姓名;
- 第一個隊伍篩選之后只要前3個人;
- 第二個隊伍只要姓張的成員姓名;
- 第二個隊伍篩選之后不要前2個人;
- 將兩個隊伍合并為一個隊伍;
- 列印整個隊伍的姓名資訊,
兩個隊伍(集合)的代碼如下:
public class Demo21ArrayListNames {
public static void main(String[] args) {
List<String> one = new ArrayList<>();
one.add("迪麗熱巴");
one.add("宋遠橋");
one.add("蘇星河");
one.add("老子");
one.add("莊子");
one.add("孫子");
one.add("洪七公");
List<String> two = new ArrayList<>();
two.add("古力娜扎");
two.add("張無忌");
two.add("張三豐");
two.add("趙麗穎");
two.add("張二狗");
two.add("張天愛");
two.add("張三");
// ....
}
}
傳統方式
使用for回圈 , 示例代碼:
public class Demo22ArrayListNames {
public static void main(String[] args) {
List<String> one = new ArrayList<>();
// ...
List<String> two = new ArrayList<>();
// ...
// 第一個隊伍只要名字為3個字的成員姓名;
List<String> oneA = new ArrayList<>();
for (String name : one) {
if (name.length() == 3) {
oneA.add(name);
}
}
// 第一個隊伍篩選之后只要前3個人;
List<String> oneB = new ArrayList<>();
for (int i = 0; i < 3; i++) {
oneB.add(oneA.get(i));
}
// 第二個隊伍只要姓張的成員姓名;
List<String> twoA = new ArrayList<>();
for (String name : two) {
if (name.startsWith("張")) {
twoA.add(name);
}
}
// 第二個隊伍篩選之后不要前2個人;
List<String> twoB = new ArrayList<>();
for (int i = 2; i < twoA.size(); i++) {
twoB.add(twoA.get(i));
}
// 將兩個隊伍合并為一個隊伍;
List<String> totalNames = new ArrayList<>();
totalNames.addAll(oneB);
totalNames.addAll(twoB);
// 列印整個隊伍的姓名資訊,
for (String name : totalNames) {
System.out.println(name);
}
}
}
運行結果為:
宋遠橋
蘇星河
洪七公
張二狗
張天愛
張三
Stream方式
等效的Stream流式處理代碼為:
public class Demo23StreamNames {
public static void main(String[] args) {
List<String> one = new ArrayList<>();
// ...
List<String> two = new ArrayList<>();
// ...
// 第一個隊伍只要名字為3個字的成員姓名;
// 第一個隊伍篩選之后只要前3個人;
Stream<String> streamOne = one.stream().filter(s -> s.length() == 3).limit(3);
// 第二個隊伍只要姓張的成員姓名;
// 第二個隊伍篩選之后不要前2個人;
Stream<String> streamTwo = two.stream().filter(s -> s.startsWith("張")).skip(2);
// 將兩個隊伍合并為一個隊伍;
// 根據姓名創建Person物件;
// 列印整個隊伍的Person物件資訊,
Stream.concat(streamOne, streamTwo).forEach(s->System.out.println(s));
}
}
運行效果完全一樣:
宋遠橋
蘇星河
洪七公
張二狗
張天愛
張三
函式拼接與終結方法
在上述介紹的各種方法中,凡是回傳值仍然為Stream介面的為函式拼接方法,它們支持鏈式呼叫;而回傳值不再為Stream介面的為終結方法,不再支持鏈式呼叫,如下表所示:
| 方法名 | 方法作用 | 方法種類 | 是否支持鏈式呼叫 |
|---|---|---|---|
| count | 統計個數 | 終結 | 否 |
| forEach | 逐一處理 | 終結 | 否 |
| filter | 過濾 | 函式拼接 | 是 |
| limit | 取用前幾個 | 函式拼接 | 是 |
| skip | 跳過前幾個 | 函式拼接 | 是 |
| concat | 組合 | 函式拼接 | 是 |
Stream流中的結果到集合中
Stream流提供 collect方法,其引數需要一個 java.util.stream.Collector<T,A, R>介面物件來指定收集到哪 種集合中,java.util.stream.Collectors 類提供一些方法,可以作為 Collector`介面的實體:
- public static
Collector<T, ?, List > toList():轉換為 List集合, - public static
Collector<T, ?, Set > toSet():轉換為 Set集合,
下面是這兩個方法的基本使用代碼
public class Test {
public static void main(String[] args) {
Stream<String> stream = Stream.of("aa", "bb", "cc");
//轉換為list集合
List<String> list = stream.collect(Collectors.toList());
//轉換為set集合
Set<String> set = stream.collect(Collectors.toSet());
//轉換為ArrayList集合
ArrayList<String> arrayList = stream.collect(Collectors.toCollection(ArrayList::new));
//轉換為HashSet集合
HashSet<String> hashSet = stream.collect(Collectors.toCollection(HashSet::new));
}
}
Stream流中的結果到陣列中
Stream提供 toArray方法來將結果放到一個陣列中,回傳值型別是Object[]的:
Object[] toArray();
其使用場景如:
public class Test {
public static void main(String[] args) {
Stream<String> stream = Stream.of("aa", "bb", "cc");
Object[] objects = stream.toArray();
System.out.println(Arrays.toString(objects));
String[] strings = stream.toArray(String[]::new);
System.out.println(Arrays.toString(strings));
}
}
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/550774.html
標籤:其他
上一篇:boot-admin整合flowable官方editor-app原始碼進行BPMN2-0建模(續)
下一篇:返回列表
