集合與數組Stream
流
Stream
流的遍歷寫法
public static void main(String[] args) {
List<String> list = new ArrayList<>();
list.add("張無忌");
list.add("周芷若");
list.add("趙敏");
list.add("張強");
list.add("張三丰");
list.stream().filter((e) -> {//lambda標準格式寫法
return e.startsWith("張");
}).filter((e)->{return e.length()==3;}).forEach((e)->{
System.out.println(e);
});//方法調用完畢返回一個流,還可以繼續調用,鏈式調用
list.stream()
.filter(s -> s.startsWith("張"))//lambda省略格式寫法
.filter(s -> s.length() == 3)
.forEach(System.out::println);//用下方法引用
}
直接閱讀代碼的字面意思即可完美展示無關邏輯方式的語義:獲取流、過濾姓張、過濾長度爲3、逐一打印。代碼中並沒有體現使用線性循環或是其他任何算法進行遍歷,我們真正要做的事情內容被更好地體現在代碼中。
獲取流,單列集合通過stream
方法,數組通過靜態of
方法
單列集合通過stream方法,獲取流
首先,java.util.Collection
接口中加入了default方法stream()
用來獲取流,所以其所有實現類均可獲取流。
public static void main(String[] args) {
List<String> list = new ArrayList<>();
// ...
Stream<String> stream1 = list.stream();//變成一個流.var
Set<String> set = new HashSet<>();
// ...
Stream<String> stream2 = set.stream();
Vector<String> vector = new Vector<>();
// ...
Stream<String> stream3 = vector.stream();
}
雙列集合先得到單列集合,再獲取流,雙列集合不能直接來,只能間接來
java.util.Map
接口不是Collection
的子接口,且其K-V數據結構不符合流元素的單一特徵Stream,所以獲取對應的流需要分key、value或entry等情況:
public static void main(String[] args) {
Map<String, String> map = new HashMap<>();//以下都是雙列集合裏面的單列集合
// ...
Stream<String> keyStream = map.keySet().stream();//鍵的集合,單列集合
Stream<String> valueStream = map.values().stream();//值的集合,單列集合
Stream<Map.Entry<String, String>> entryStream = map.entrySet().stream();//鍵值對對象集合
}
流靜態of方法,傳入數組,獲取流
如果使用的不是集合而是數組,由於數組對象不可能添加默認方法,所以Stream
接口中提供了靜態方法of
,使用很簡單:
import java.util.stream.Stream;
public class Demo06GetStream {
public static void main(String[] args) {
String[] array = { "張無忌", "張翠山", "張三丰", "張一元" };
Stream<String> stream = Stream.of(array);
Stream<String> ss = Stream.of("fbb", "jjjjjg", "jjj");
}
}
備註:
of
方法的參數其實是一個可變參數,本質是一個數組,所以支持數組。
常用方法
流模型的操作(方法)很豐富,這裏介紹一些常用的API。這些方法可以被分成兩種:
- 終結方法:返回值類型不再是
Stream
接口自身類型的方法,因此不再支持類似StringBuilder
那樣的鏈式調用。本小節中,終結方法包括count
和forEach
方法。 - 非終結方法:返回值類型仍然是
Stream
接口自身類型的方法,因此支持鏈式調用。(除了終結方法外,其餘方法均爲非終結方法。)
過濾:filter
,過濾條件爲真,產生一個新的流
可以通過filter
方法將一個流轉換成另一個子集流。方法簽名:
Stream<T> filter(Predicate<? super T> predicate);//根據條件過濾元素,到一個新的流裏面,產生一個新的流
該接口接收一個Predicate
函數式接口參數(可以是一個Lambda或方法引用)作爲篩選條件。
Predicate接口
java.util.stream.Predicate
函數式接口,其中唯一的抽象方法爲:
boolean test(T t);
該方法將會產生一個boolean值結果,代表指定的條件是否滿足。如果結果爲true,那麼Stream流的filter
方法將會留用元素;如果結果爲false,那麼filter
方法將會捨棄元素。
基本使用
Stream流中的filter
方法基本使用的代碼如:
public static void main(String[] args) {
Stream<String> original = Stream.of("張無忌", "張三丰", "周芷若");
Stream<String> result = original.filter((s) -> {return s.startsWith("張");});//標準格式
//Stream<String> result = original.filter(s -> s.startsWith("張"));//省略格式
}
在這裏通過Lambda表達式來指定了篩選的條件:必須姓張。
統計個數:count,統計流中元素個數
正如舊集合Collection
當中的size
方法一樣,流提供count
方法來數一數其中的元素個數:
long count();
該方法返回一個long值代表元素個數(不再像舊集合那樣是int值)。基本使用:
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);
參數是一個long型,如果集合當前長度大於參數則進行截取;否則不進行操作。基本使用:
import java.util.stream.Stream;
public class Demo10StreamLimit {
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);
如果流的當前長度大於n,則跳過前n個;否則跳過所有,將會得到一個長度爲0的空流。基本使用:
import java.util.stream.Stream;
public class Demo11StreamSkip {
public static void main(String[] args) {
Stream<String> original = Stream.of("張無忌", "張三丰", "周芷若");
Stream<String> result = original.skip(2);
System.out.println(result.count()); // 1
}
}
(數據轉換)映射:map,把調用方法的流變成另一個流,即把流的元素類型轉換改變即可
如果需要將流中的元素映射到另一個流中,可以使用map
方法。方法簽名:
<R> Stream<R> map(Function<? super T, ? extends R> mapper);
該接口需要一個Function
函數式接口參數,可以將當前流中的T類型數據轉換爲另一種R類型的流。
Function接口
此前我們已經學習過java.util.stream.Function
函數式接口,其中唯一的抽象方法爲:
R apply(T t);//通過T類型得到R類型東西,把一個東西變成轉換成另一個東西,綁定死了,t綁定r,對應關係,映射
這可以將一種T類型轉換成爲R類型,而這種轉換的動作,就稱爲“映射”。//理解,t綁定r,對應關係,映射
基本使用
Stream流中的map
方法基本使用的代碼如:
public static void main(String[] args) {
Stream<String> original = Stream.of("10", "12", "18");
//Stream<Integer> result = original.map(s -> Integer.valueOf(s));
//把字符串元素變成整數,新的流
Stream<Integer> result = original.map(new Function<String, Integer>() {
@Override
public Integer apply(String s) {
return Integer.valueOf(s);
}
});
System.out.println(result.collect(Collectors.toList())); // [10, 12, 18]
}
這段代碼中,map
方法的參數通過方法引用,將字符串類型轉換成爲了int類型(並自動裝箱爲Integer
類對象)。
組合(合併):concat,靜態方法,傳入兩個流,進行合併
如果有兩個流,希望合併成爲一個流,那麼可以使用Stream
接口的靜態方法concat
:
static <T> Stream<T> concat(Stream<? extends T> a, Stream<? extends T> b)
備註:這是一個靜態方法,與
java.lang.String
當中的concat
方法是不同的。
該方法的基本使用代碼如:
public class Demo12StreamConcat {
public static void main(String[] args) {
Stream<String> streamA = Stream.of("張無忌");
Stream<String> streamB = Stream.of("張翠山");
Stream<String> result = Stream.concat(streamA, streamB);
}
}
遍歷: forEach
,消費每一個元素,一般用於打印元素
雖然方法名字叫forEach
,但是與增強for循環中的“for-each”暱稱不同,該方法並不保證元素的逐一消費動作,在流中是被有序執行的。
void forEach(Consumer<? super T> action);
該方法接收一個Consumer
接口函數,會將每一個流元素交給該函數進行處理。例如:
public static void main(String[] args) {
Stream<String> stream = Stream.of("張無忌", "張三丰", "周芷若");
//stream.forEach(System.out::println);
stream.forEach(new Consumer<String>() {
@Override
public void accept(String s) {
System.out.println(s);
}
});
}
在這裏,方法引用System.out::println
就是一個Consumer
函數式接口的實例(對象)。
總結:函數拼接與終結方法
在上述介紹的各種方法中,凡是返回值仍然爲Stream
接口的爲函數拼接方法,它們支持鏈式調用;而返回值不再爲Stream
接口的爲終結方法,不再支持鏈式調用。如下表所示:
方法名 | 方法作用 | 方法種類 | 是否支持鏈式調用 |
---|---|---|---|
count | 統計個數 | 終結 | 否 |
forEach | 逐一處理 | 終結 | 否 |
filter | 過濾 | 函數拼接 | 是 |
skip | 跳過前幾個 | 函數拼接 | 是 |
map | 映射 | 函數拼接 | 是 |
concat | 組合 | 函數拼接 | 是 |
limit | 取用前幾個 | 函數拼接 | 是 |
Stream
流轉換爲集合或者數組
對流操作完成之後,如果需要將其結果進行收集,例如獲取對應的集合、數組等,如何操作?
流的collect收集方法(自動選你想要的),傳入收集工具類調用方法轉換爲集合
直接用,下面的這一波,有空了解一下,看看就好:
Stream流提供collect
方法,其參數需要一個java.util.stream.Collector<T,A, R>
接口對象來指定收集到哪種集合中。
幸運的是,java.util.stream.Collectors
收集器工具類提供一些方法,可以作爲Collector
接口的實例:
public static <T> Collector<T, ?, List<T>> toList()
:轉換爲List
集合。public static <T> Collector<T, ?, Set<T>> toSet()
:轉換爲Set
集合。
下面是這兩個方法的基本使用代碼:
public static void main(String[] args) {
Stream<String> stream = Stream.of("10", "20", "30", "40", "50");
//流收集方法,傳入收集工具類調用方法轉換爲集合
List<String> arr = stream.collect(Collectors.toList());//流已經變成一個集合,到達終點不能再變,流一去不復返!!!
for (String s : arr) {
System.out.println(s);
}
//所以,下面不能同時來要註釋一下,否則報IllegalStateException: stream has already been operated upon or closed
// Set<String> set = stream.collect(Collectors.toSet());
// for (String s : set) {
// System.out.println(s);
// }
//java.util.stream.Collectors收集器工具類,更優美的字符串拼接
String[] arr = {"12","13","15","16"};
String str = Arrays.stream(arr) //間隔 前綴 後綴
.collect(Collectors.joining("@","[","]"));
// .collect(Collectors.joining("@"));
System.out.println(str); //[12@13@15@16]
}
流調用方法轉換爲數組
Stream提供toArray
方法來將結果放到一個數組中,返回值類型是Object[]的:
Object[] toArray();
其使用場景如:
import java.util.stream.Stream;
public class Demo16StreamArray {
public static void main(String[] args) {
Stream<String> stream = Stream.of("10", "20", "30", "40", "50");
Object[] arr = stream.toArray();
for (Object o : arr) {
System.out.println(o);
}
}
}
併發流,流併發,都是同一個意思,讓流實現併發操作
對於Stream流來說,這很簡單。
流的併發方法,跟下面的單列集合的併發流效果,本質上是一樣的,都是併發
Stream
的父接口java.util.stream.BaseStream
中定義了一個parallel
方法:
S parallel();//parallel併發的意思
只需要在流上調用一下無參數的parallel
方法,那麼當前流即可變身爲支持併發操作的流,返回值仍然爲Stream
類型。例如:
import java.util.stream.Stream;
public class Demo13StreamParallel {
public static void main(String[] args) {
Stream<Integer> stream = Stream.of(10, 20, 30, 40, 50).parallel();//流的併發方法
//流的併發方法,跟下面的單列集合的併發流效果,本質上是一樣的,都是併發
stream.forEach((e)->{
System.out.println(e);//每次運行的結果有可能都不太一樣,併發操作,一會執行這個,一個執行那個
});
}
}
單列集合的併發流方法
在通過集合獲取流時,也可以直接調用parallelStream
方法來直接獲取支持併發操作的流。方法定義爲:
default Stream<E> parallelStream() {...}
應用代碼爲:
import java.util.ArrayList;
import java.util.stream.Stream;
public class Demo13StreamParallel {
public static void main(String[] args) {
ArrayList<String> al = new ArrayList<>();
al.add("wo");
al.add("ai");
al.add("ni");
Stream<String> stream = al.parallelStream();//單列集合的併發流方法
//每次運行的結果有可能都不太一樣,併發操作,一會執行這個,一個執行那個
stream.forEach(System.out::println);//方法引用,打印參數,這裏是的參數是元素,即打印元素
}
}
擴展:流調用方法轉換爲數組,並傳入數組構造器引用,指定對應類型的數組
有了Lambda和方法引用之後,可以使用toArray
方法的另一種重載形式傳遞一個IntFunction<A[]>
的函數,繼而從外面指定泛型參數。方法簽名:
<A> A[] toArray(IntFunction<A[]> generator);//需要一個數組的構造器引用,構造一個指定類型的數組出來!!!
有了它,上例代碼中不再侷限於Object[]
結果,而可以得到String[]
結果:
import java.util.stream.Stream;
public class Demo17StreamArray {
public static void main(String[] args) {
Stream<String> stream = Stream.of("10", "20", "30", "40", "50");
//String[] arr = stream.toArray(value -> new String[value]);
String[] arr = stream.toArray(String[]::new);//new String[];
for (String s : arr) {
System.out.println(s);
}
}
}
//既然數組也是有構造器的,那麼傳遞一個數組的構造器引用,即可構造一個對應類型的數組出來
在Lambda標準格式()->{}的基礎上,使用省略寫法的規則爲:
- 小括號內參數的類型可以省略;
- 如果小括號內有且僅有一個參數,則小括號可以省略;
- 如果大括號內有且僅有一個語句,則無論是否有返回值,都可以省略大括號、return關鍵字及語句分號。
stream流其他教材博客資料:
stream.toArray(數組的構造器)
lambda表達式:構造器引用資料