天天看點

一文帶你入門Java Stream流,太強了

一文帶你入門Java Stream流,太強了

兩個星期以前,就有讀者強烈要求我寫一篇 Java Stream 流的文章,我說市面上不是已經有很多了嗎,結果你猜他怎麼說:“就想看你寫的啊!”你看你看,多麼蒼白的喜歡啊。那就“勉為其難”寫一篇吧,嘻嘻。

單從“Stream”這個單詞上來看,它似乎和 java.io 包下的 InputStream 和 OutputStream 有些關系。實際上呢,沒毛關系。Java 8 新增的 Stream 是為了解放程式員操作集合(Collection)時的生産力,之是以能解放,很大一部分原因可以歸功于同時出現的 Lambda 表達式——極大的提高了程式設計效率和程式可讀性。

Stream 究竟是什麼呢?

Stream 就好像一個進階的疊代器,但隻能周遊一次,就好像一江春水向東流;在流的過程中,對流中的元素執行一些操作,比如“過濾掉長度大于 10 的字元串”、“擷取每個字元串的首字母”等。

要想操作流,首先需要有一個資料源,可以是數組或者集合。每次操作都會傳回一個新的流對象,友善進行鍊式操作,但原有的流對象會保持不變。

流的操作可以分為兩種類型:

1)中間操作,可以有多個,每次傳回一個新的流,可進行鍊式操作。

2)終端操作,隻能有一個,每次執行完,這個流也就用光光了,無法執行下一個操作,是以隻能放在最後。

來舉個例子。

List list = new ArrayList<>();

list.add("武漢加油");

list.add("中國加油");

list.add("世界加油");

long count = list.stream().distinct().count();

System.out.println(count);

distinct() 方法是一個中間操作(去重),它會傳回一個新的流(沒有共同元素)。

Stream distinct();

count() 方法是一個終端操作,傳回流中的元素個數。

long count();

中間操作不會立即執行,隻有等到終端操作的時候,流才開始真正地周遊,用于映射、過濾等。通俗點說,就是一次周遊執行多個操作,性能就大大提高了。

理論部分就扯這麼多,下面直接進入實戰部分。

01、建立流

如果是數組的話,可以使用 Arrays.stream() 或者 Stream.of() 建立流;如果是集合的話,可以直接使用 stream() 方法建立流,因為該方法已經添加到 Collection 接口中。

public class CreateStreamDemo {

    public static void main(String[] args) {

        String[] arr = new String[]{"武漢加油", "中國加油", "世界加油"};

        Stream stream = Arrays.stream(arr);

        stream = Stream.of("武漢加油", "中國加油", "世界加油");

        List list = new ArrayList<>();

        list.add("武漢加油");

        list.add("中國加油");

        list.add("世界加油");

        stream = list.stream();

    }

}

檢視 Stream 源碼的話,你會發現 of() 方法内部其實調用了 Arrays.stream() 方法。

public static Stream of(T... values) {

    return Arrays.stream(values);

另外,集合還可以調用 parallelStream() 方法建立并發流,預設使用的是 ForkJoinPool.commonPool()線程池。

List aList = new ArrayList<>();

Stream parallelStream = aList.parallelStream();

02、操作流

Stream 類提供了很多有用的操作流的方法,我來挑一些常用的給你介紹一下。

1)過濾

通過 filter() 方法可以從流中篩選出我們想要的元素。

public class FilterStreamDemo {

        list.add("周傑倫");

        list.add("王力宏");

        list.add("陶喆");

        list.add("林俊傑");

        Stream stream = list.stream().filter(element -> element.contains("王"));

        stream.forEach(System.out::println);

filter() 方法接收的是一個 Predicate(Java 8 新增的一個函數式接口,接受一個輸入參數傳回一個布爾值結果)類型的參數,是以,我們可以直接将一個 Lambda 表達式傳遞給該方法,比如說 element -> element.contains("王") 就是篩選出帶有“王”的字元串。

forEach() 方法接收的是一個 Consumer(Java 8 新增的一個函數式接口,接受一個輸入參數并且無傳回的操作)類型的參數,類名 :: 方法名是 Java 8 引入的新文法,System.out 傳回 PrintStream 類,println 方法你應該知道是列印的。

stream.forEach(System.out::println); 相當于在 for 循環中列印,類似于下面的代碼:

for (String s : strs) {

    System.out.println(s);

很明顯,一行代碼看起來更簡潔一些。來看一下程式的輸出結果:

王力宏

2)映射

如果想通過某種操作把一個流中的元素轉化成新的流中的元素,可以使用 map() 方法。

public class MapStreamDemo {

        Stream stream = list.stream().map(String::length);

map() 方法接收的是一個 Function(Java 8 新增的一個函數式接口,接受一個輸入參數 T,傳回一個結果 R)類型的參數,此時參數 為 String 類的 length 方法,也就是把 Stream 的流轉成一個 Stream 的流。

程式輸出的結果如下所示:

3

2

3)比對

Stream 類提供了三個方法可供進行元素比對,它們分别是:

anyMatch(),隻要有一個元素比對傳入的條件,就傳回 true。

allMatch(),隻有有一個元素不比對傳入的條件,就傳回 false;如果全部比對,則傳回 true。

noneMatch(),隻要有一個元素比對傳入的條件,就傳回 false;如果全部比對,則傳回 true。

public class MatchStreamDemo {

        boolean  anyMatchFlag = list.stream().anyMatch(element -> element.contains("王"));

        boolean  allMatchFlag = list.stream().allMatch(element -> element.length() > 1);

        boolean  noneMatchFlag = list.stream().noneMatch(element -> element.endsWith("沉"));

        System.out.println(anyMatchFlag);

        System.out.println(allMatchFlag);

        System.out.println(noneMatchFlag);

因為“王力宏”以“王”字開頭,是以 anyMatchFlag 應該為 true;因為“周傑倫”、“王力宏”、“陶喆”、“林俊傑”的字元串長度都大于 1,是以 allMatchFlag 為 true;因為 4 個字元串結尾都不是“沉”,是以 noneMatchFlag 為 true。

true

4)組合

reduce() 方法的主要作用是把 Stream 中的元素組合起來,它有兩種用法:

Optional reduce(BinaryOperator accumulator)

沒有起始值,隻有一個參數,就是運算規則,此時傳回 Optional。

T reduce(T identity, BinaryOperator accumulator)

有起始值,有運算規則,兩個參數,此時傳回的類型和起始值類型一緻。

來看下面這個例子。

public class ReduceStreamDemo {

        Integer[] ints = {0, 1, 2, 3};

        List list = Arrays.asList(ints);

        Optional optional = list.stream().reduce((a, b) -> a + b);

        Optional optional1 = list.stream().reduce(Integer::sum);

        System.out.println(optional.orElse(0));

        System.out.println(optional1.orElse(0));

        int reduce = list.stream().reduce(6, (a, b) -> a + b);

        System.out.println(reduce);

        int reduce1 = list.stream().reduce(6, Integer::sum);

        System.out.println(reduce1);

運算規則可以是 Lambda 表達式(比如 (a, b) -> a + b),也可以是類名::方法名(比如 Integer::sum)。

程式運作的結果如下所示:

6

12

0、1、2、3 在沒有起始值相加的時候結果為 6;有起始值 6 的時候結果為 12。

03、轉換流

既然可以把集合或者數組轉成流,那麼也應該有對應的方法,将流轉換回去——collect() 方法就滿足了這種需求。

public class CollectStreamDemo {

        String[] strArray = list.stream().toArray(String[]::new);

        System.out.println(Arrays.toString(strArray));

        List list1 = list.stream().map(String::length).collect(Collectors.toList());

        List list2 = list.stream().collect(Collectors.toCollection(ArrayList::new));

        System.out.println(list1);

        System.out.println(list2);

        String str = list.stream().collect(Collectors.joining(", ")).toString();

        System.out.println(str);

toArray() 方法可以将流轉換成數組,你可能比較好奇的是 String[]::new,它是什麼東東呢?來看一下 toArray() 方法的源碼。

 A[] toArray(IntFunction

 generator);

也就是說 String[]::new 是一個 IntFunction,一個可以産生所需的新數組的函數,可以通過反編譯位元組碼看看它到底是什麼:

String[] strArray = (String[])list.stream().toArray((x$0) -> {

    return new String[x$0];

});

System.out.println(Arrays.toString(strArray));

也就是相當于傳回了一個指定長度的字元串數組。

當我們需要把一個集合按照某種規則轉成另外一個集合的時候,就可以配套使用 map() 方法和 collect()方法。

List list1 = list.stream().map(String::length).collect(Collectors.toList());

通過 stream() 方法建立集合的流後,再通過 map(String:length) 将其映射為字元串長度的一個新流,最後通過 collect() 方法将其轉換成新的集合。

Collectors 是一個收集器的工具類,内置了一系列收集器實作,比如說 toList() 方法将元素收集到一個新的 java.util.List 中;比如說 toCollection() 方法将元素收集到一個新的 java.util.ArrayList 中;比如說 joining() 方法将元素收集到一個可以用分隔符指定的字元串中。

來看一下程式的輸出結果:

[周傑倫, 王力宏, 陶喆, 林俊傑]

[3, 3, 2, 3]

周傑倫, 王力宏, 陶喆, 林俊傑

04、鳴謝

好了,我親愛的讀者朋友,以上就是本文的全部内容了,是不是感覺 Stream 流實在是太強大了?我是沉默王二,一枚有趣的程式員。原創不易,莫要白票,請你為本文點贊個吧,這将是我寫作更多優質文章的最強動力。

原文位址

https://www.cnblogs.com/qing-gee/p/12610768.html