一:概念
1,Stream是元素的集合,這點讓Stream看起來用些類似Iterator;
2,可以支持順序和並行的對原Stream進行匯聚的操作;
Stream就相當於一個高級版本的Iterator。原始版本的Iterator,用戶只能一個一個的遍歷元素並對其執行某些操作;高級版本的Stream,用戶只要給出需要對其包含的元素執行什麼操作,比如“過濾掉長度大於10的字符串”、“獲取每個字符串的首字母”等,具體這些操作如何應用到每個元素上,就給Stream就好了
二:代碼對比:
創建集合:
- 首先篩選所有姓張的人;
- 然後篩選名字有三個字的人;
- 最後進行對結果進行打印輸出。
public class NormalFilter {
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);
}
}
}
Stream流實現優雅的代碼書寫:
public class StreamFilter {
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));
}
}
三:流式思想概述
這張圖展示了Stream流的過濾,映射,跳過,計數等多步操作,這是一種集合元素的處理方案,而方案就是一種“函數模型”。圖中的每一個方框都是一個“流”,調用指定的方法,可以從一個流模型轉換爲另一個流模型。而最右側的數字3是最終結果。
這裏的filter、map、skip都是在對函數模型進行操作,集合元素並沒有真正被處理。只有當終結方法count執行的時候,整個模型纔會按照指定策略執行操作。而這得益於Lambda的延遲執行特性。
備註:“Stream流”其實是一個集合元素的函數模型,它並不是集合,也不是數據結構,其本身並不存儲任何元素(或其地址值)。
四:如何獲取流:
java.util.stream.Stream是Java 8新加入的最常用的流接口。(這並不是一個函數式接口。)
獲取一個流非常簡單,有以下幾種常用的方式:
- 所有的Collection集合都可以通過stream默認方法獲取流;
- Stream接口的靜態方法of可以獲取數組對應的流。
分爲三種情況:單列集合(list,set),雙列集合(Map),數組
方式1 : 根據Collection獲取流
首先,java.util.Collection接口中加入了default方法stream用來獲取流,所以其所有實現類均可獲取流。
import java.util.*;
import java.util.stream.Stream ;
public class GetStream {
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();
Vector<String> vector = new Vector<>();
// ...
Stream<String> stream3 = vector.stream();
}
}
方式2 : 根據Map獲取流
java.util.Map接口不是Collection的子接口,且其K-V數據結構不符合流元素的單一特徵,所以獲取對應的流需要分key、value或entry等情況:
import java.util.HashMap;
import java.util.Map;
import java.util.stream.Stream;
public class MapGetStream {
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();
}
}
方式3 : 根據數組獲取流
如果使用的不是集合或映射而是數組,由於數組對象不可能添加默認方法,所以Stream接口中提供了靜態方法of,使用很簡單:
import java.util.stream.Stream;
public class ArrGetStream {
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 Demo12StreamForEach {
public static void main(String[] args) {
Stream<String> stream = Stream.of("張無忌", "張三丰", "周芷若");
stream.forEach(s->System.out.println(s));
}
}
在這裏,方法引用System.out::println就是一個Consumer函數式接口的示例。
count:統計個數
正如舊集合Collection當中的size方法一樣,流提供count方法來數一數其中的元素個數:
long count();
該方法返回一個long值代表元素個數(不再像舊集合那樣是int值)。基本使用:
public class StreamCount {
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
}
}
filter:過濾
可以通過filter方法將一個流轉換成另一個子集流。方法聲明:
Stream<T> filter(Predicate<? super T> predicate);
該接口接收一個Predicate函數式接口參數(可以是一個Lambda或方法引用)作爲篩選條件。
基本使用
Stream流中的filter方法基本使用的代碼如:
public class Demo07StreamFilter {
public static void main(String[] args) {
Stream<String> original = Stream.of("張無忌", "張三丰", "周芷若");
Stream<String> result = original.filter(s -> s.startsWith("張"));
}
}
在這裏通過Lambda表達式來指定了篩選的條件:必須姓張。
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 StreamSkip {
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類型的流。
基本使用
Stream流中的map方法基本使用的代碼如:
import java.util.stream.Stream;
public class DemoStreamMap {
public static void main(String[] args) {
Stream<String> original = Stream.of("10", "12", "18");
Stream<Integer> result = original.map(Integer::parseInt);
}
}
這段代碼中,map方法的參數通過方法引用,將字符串類型轉換成爲了int類型(並自動裝箱爲Integer類對象)。
concat:組合
如果有兩個流,希望合併成爲一個流,那麼可以使用Stream接口的靜態方法concat:
static <T> Stream<T> concat(Stream<? extends T> a, Stream<? extends T> b)
備註:這是一個靜態方法,與java.lang.String當中的concat方法是不同的。
該方法的基本使用代碼如:
import java.util.stream.Stream;
public class DemoStreamConcat {
public static void main(String[] args) {
Stream<String> streamA = Stream.of("張無忌");
Stream<String> streamB = Stream.of("張翠山");
Stream<String> result = Stream.concat(streamA, streamB);
}
}