Java8 - 使用Stream API

Java8 - 使用Stream APIjava

Stream 是什麼?

Stream represents a sequence of objects from a source, which supports aggregate operations(多種操做). Following are the characteristics(特徵) of a Stream −api

  • Sequence of elements − A stream provides a set of elements of specific type in a sequential manner. A stream gets/computes elements on demand. It never stores the elements.數組

  • Source − Stream takes Collections, Arrays, or I/O resources as input source.ide

  • Aggregate operations − Stream supports aggregate operations(多種操做) like filter, map, limit, reduce, find, match, and so on.函數

  • Pipelining − Most of the stream operations return stream itself so that their result can be pipelined. These operations are called intermediate operations and their function is to take input, process them, and return output to the target. collect() method is a terminal operation(終端操做) which is normally present at the end of the pipelining operation to mark the end of the stream.ui

  • Automatic iterations − Stream operations do the iterations internally over the source elements provided, in contrast to Collections where explicit iteration is required.this

 

流是如何建立的?

流有多種建立方式,好比最多見的經過集合,數組spa

經過集合構建流

package com.common.stream;

import java.util.Arrays;
import java.util.List;
import java.util.function.Supplier;
import java.util.stream.Stream;

public class CreateStreamTest {

    private static final List<Dish> menu = Arrays.asList(
            new Dish("pork", 800, Dish.Type.MEAT),
            new Dish("beef", 700, Dish.Type.MEAT),
            new Dish("chicken", 400, Dish.Type.MEAT),
            new Dish("french fries", 530, Dish.Type.OTHER),
            new Dish("rice", 350, Dish.Type.OTHER),
            new Dish("season fruit", 120, Dish.Type.OTHER),
            new Dish("pizza", 550, Dish.Type.OTHER),
            new Dish("prawns", 300, Dish.Type.FISH),
            new Dish("salmon", 450, Dish.Type.FISH));

    public static void main(String[] args) {
        //獲取一個stream 流
        Stream<Dish> stream = menu.stream();
        stream.peek(x -> System.out.println("from stream: " + x)).
                filter((x) -> x.getCalories() > 500).
                peek(x -> System.out.println("after filter: " + x)).
                forEach(System.out::println);

        //獲取一個並行的流
        Stream parallelStream = menu.parallelStream();

    }
}

 

上面這段代碼輸出打印,code

from stream: Dish{name='pork', calories=800}
after filter: Dish{name='pork', calories=800}
Dish{name='pork', calories=800}
from stream: Dish{name='beef', calories=700}
after filter: Dish{name='beef', calories=700}
Dish{name='beef', calories=700}
from stream: Dish{name='chicken', calories=400}
from stream: Dish{name='french fries', calories=530}
after filter: Dish{name='french fries', calories=530}
Dish{name='french fries', calories=530}
from stream: Dish{name='rice', calories=350}
from stream: Dish{name='season fruit', calories=120}
from stream: Dish{name='pizza', calories=550}
after filter: Dish{name='pizza', calories=550}
Dish{name='pizza', calories=550}
from stream: Dish{name='prawns', calories=300}
from stream: Dish{name='salmon', calories=450}

從輸出來看,Stream 流至關於一個迭代器,依次遍歷全部的元素。orm

經過數組構建流

可使用靜態方法Arrays.stream從數組建立一個流。它接受一個數組做爲參數。

String[] arrayOfWords = {"Goodbye", "World"};
Stream<String> streamOfwords = Arrays.stream(arrayOfWords);

int[] numbers = {2, 3, 5, 7, 11, 13};
int sum = Arrays.stream(numbers).sum();

經過Stream.of(傳遞顯示值)建立流

可使用靜態方法Stream.of,經過顯式值建立一個流。它能夠接受任意數量的參數。例 如,如下代碼直接使用Stream.of建立了一個字符串流。而後,能夠將字符串轉換爲大寫,再 一個個打印出來:

Stream.of("hello", "world").map(String::toUpperCase).forEach(System.out::println);

經過文件建立流 

Java中用於處理文件等I/O操做的NIO API(非阻塞 I/O)已更新,以便利用Stream API。 java.nio.file.Files中的不少靜態方法都會返回一個流。例如,一個頗有用的方法是 Files.lines,它會返回一個由指定文件中的各行構成的字符串流。

long uniqueWords = 0;
try (Stream<String> lines = Files.lines(Paths.get("/Users/flyme/workspace/showcase/common/src/main/java/data.txt"), Charset.defaultCharset())) {
    uniqueWords = lines.flatMap(line -> Arrays.stream(line.split(" "))).distinct()
            .count();

    System.out.println(uniqueWords);
} catch (IOException e) {
    e.printStackTrace();
}

由函數生成流:建立無限流

Stream API提供了兩個靜態方法來從函數生成流:Stream.iterate和Stream.generate。 這兩個操做能夠建立所謂的無限流:不像從定集合建立的流那樣有定大小的流。由iterate 2 和generate產生的流會用給定的函數按需建立值,所以能夠無無盡地計算下去!通常來講, 應該使用limit(n)來對這種流加以限制,以免打印無窮多個值。

package com.common.stream;

import java.util.function.Supplier;
import java.util.stream.Stream;

public class StreamGenerateTest {

    static class NaturalSupplier implements Supplier<Long> {

        long value = 0;

        @Override
        public Long get() {
            this.value = this.value + 1;
            return this.value;
        }
    }

    public static void main(String[] args) {


//        iterate方法接受一個初始值(在這裏是0),還有一個依次應用在每一個產生的新值上的 Lambda(UnaryOperator<t>類型)。
//        這裏,咱們使用Lambda n -> n + 2,返回的是前一個元 素加上2。
//        所以,iterate方法生成了一個全部正偶數的流:流的第一個元素是初始值0。
//        而後加 上2來生成新的值2,再加上2來獲得新的值4,以此類推。
//        這種iterate操做基本上是順序的, 由於結果取決於前一次應用。
//        請注意,此操做將生成一個無限流——這個流沒有結,由於值是 按需計算的,能夠遠計算下去。
//        咱們說這個流是無界的。正如咱們前面所討論的,這是流和集 合之間的一個關鍵區別。咱們使用limit方法來顯式限制流的大小。
//        這裏只選擇了前10個偶數。 8 而後能夠調用forEach終端操做來消費流,並分別打印每一個元素。
        Stream.iterate(0, n -> n + 2)
                .limit(10)
                .forEach(System.out::println);


        //建立一個無窮數列的stream
        Stream<Long> natural = Stream.generate(new NaturalSupplier());
        //使用limit 方法截斷流
        natural.peek(n -> {
            System.out.println("from stream:" + n);
        }).map((x) -> x * x).limit(10).forEach(System.out::println);

    }
}

 

Stream 和集合的不一樣

一是集合類持有的全部元素都是存儲在內存中的,很是巨大的集合類會佔用大量的內存,而Stream的元素倒是在訪問的時候才被計算出來,這種「延遲計算」的特性有點相似Clojure的lazy-seq,佔用內存不多。

二是集合能夠遍歷屢次,而流只能遍歷一次,遍歷完以後,咱們就說這個流已經被消費掉了。 你能夠從原始數據源那裏再得到一個新的流來從新遍歷一遍,就像迭代器同樣(這裏假設它是集 合之類的可重複的源,若是是I/O通道就沒戲了)。

三是集合類的迭代邏輯是調用者負責,一般是for循環,這稱爲外部迭代;而Stream的迭代是隱含在對Stream的各類操做中,例如map(),稱爲內部迭代。

示例一,經過Stream建立一個無窮大小的天然數集合,使用 Supplier 能夠建立一個無窮大小的 Stream。若是用集合是作不到的。

package com.common.stream;

import java.util.function.Supplier;
import java.util.stream.Stream;

public class CreateStreamTest {

    static class NaturalSupplier implements Supplier<Long> {

        long value = 0;

        @Override
        public Long get() {
            this.value = this.value + 1;
            return this.value;
        }
    }

    public static void main(String[] args) {

        //建立一個無窮數列的stream
        Stream<Long> natural = Stream.generate(new NaturalSupplier());
        //使用limit 方法截斷流
        natural.peek(n -> {
            System.out.println("from stream:" + n);
        }).map((x) -> x * x).limit(10).forEach(System.out::println);

    }
}

 

示例二,Stream 只能遍歷一次,若是遍歷屢次,會報錯

package com.common.stream;

import java.util.Arrays;
import java.util.List;
import java.util.stream.Stream;

public class StreamIterationTest {

    private static final List<Dish> menu = Arrays.asList(
            new Dish("pork", 800, Dish.Type.MEAT),
            new Dish("beef", 700, Dish.Type.MEAT),
            new Dish("chicken", 400, Dish.Type.MEAT),
            new Dish("french fries", 530, Dish.Type.OTHER),
            new Dish("rice", 350, Dish.Type.OTHER),
            new Dish("season fruit", 120, Dish.Type.OTHER),
            new Dish("pizza", 550, Dish.Type.OTHER),
            new Dish("prawns", 300, Dish.Type.FISH),
            new Dish("salmon", 450, Dish.Type.FISH));

    public static void main(String[] args) {
        Stream<Dish> stream = menu.stream();
        stream.peek(x -> System.out.println("from stream: " + x)).
                filter((x) -> x.getCalories() > 500).
                peek(x -> System.out.println("after filter: " + x)).
                forEach(System.out::println);

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

輸出,

Exception in thread "main" java.lang.IllegalStateException: stream has already been operated upon or closed
    at java.util.stream.AbstractPipeline.sourceStageSpliterator(AbstractPipeline.java:279)
    at java.util.stream.ReferencePipeline$Head.forEach(ReferencePipeline.java:580)
    at com.common.stream.StreamIterationTest.main(StreamIterationTest.java:27)

 

示例三,集合和Stream遍歷的不一樣

package com.common.stream;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Iterator;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.Stream;

public class StreamIterationTest {

    private static final List<Dish> menu = Arrays.asList(
            new Dish("pork", 800, Dish.Type.MEAT),
            new Dish("beef", 700, Dish.Type.MEAT),
            new Dish("chicken", 400, Dish.Type.MEAT),
            new Dish("french fries", 530, Dish.Type.OTHER),
            new Dish("rice", 350, Dish.Type.OTHER),
            new Dish("season fruit", 120, Dish.Type.OTHER),
            new Dish("pizza", 550, Dish.Type.OTHER),
            new Dish("prawns", 300, Dish.Type.FISH),
            new Dish("salmon", 450, Dish.Type.FISH));

    public static void main(String[] args) {
        List<String> names = new ArrayList<>();

        // for each 循環外部迭代
        for (Dish dish : menu) {
            names.add(dish.getName());
        }

        // 外部迭代方式
        names = new ArrayList<>();
        Iterator<Dish> iterator = menu.iterator();
        while (iterator.hasNext()) {
            Dish d = iterator.next();
            names.add(d.getName());
        }

        names.clear();

        // 使用stream 的內部迭代
        Stream<Dish> stream = menu.stream();
        stream.peek(x -> System.out.println("from stream: " + x)).
                map((x) -> x.getName()).collect(Collectors.toList());

    }
}

參考:https://www.tutorialspoint.com/java8/java8_streams.htm

http://www.infoq.com/cn/articles/java8-new-features-new-stream-api

 

Stream 操做

stream的操做分爲爲兩類,中間操做(intermediate operations)和結束操做(terminal operations),兩者特色是:

  1. 中間操做老是會惰式執行,調用中間操做只會生成一個標記了該操做的新stream,僅此而已。
  2. 結束操做會觸發實際計算,計算髮生時會把全部中間操做積攢的操做以pipeline的方式執行,這樣能夠減小迭代次數。計算完成以後stream就會失效。

篩選和切片操做

看示例代碼,

package com.common.stream;


import java.util.Arrays;
import java.util.List;

public class StreamOperateTest {

    private static final List<Dish> menu = Arrays.asList(
            new Dish("pork", 800, Dish.Type.MEAT),
            new Dish("beef", 700, Dish.Type.MEAT),
            new Dish("chicken", 400, Dish.Type.MEAT),
            new Dish("french fries", 530, Dish.Type.OTHER),
            new Dish("rice", 350, Dish.Type.OTHER),
            new Dish("season fruit", 120, Dish.Type.OTHER),
            new Dish("pizza", 550, Dish.Type.OTHER),
            new Dish("prawns", 300, Dish.Type.FISH),
            new Dish("salmon", 450, Dish.Type.FISH));

    public static void main(String[] args) {

        // filter 過濾操做
        menu.stream().filter(n ->
                n.getCalories() > 500
        ).forEach(System.out::println);

        // distinct 去重操做
        Arrays.asList(1, 2, 1, 3, 3, 2, 4).stream().filter(i -> i % 2 == 0).distinct().forEach(System.out::println);

        // limit 截斷流的操做 只保留前兩個元素
        menu.stream().filter(n ->
                n.getCalories() > 200
        ).limit(2).forEach(System.out::println);

        // skip 跳過操做 跳過前兩個元素,返回剩下的元素
        menu.stream().filter(n ->
                n.getCalories() > 200
        ).skip(2).forEach(System.out::println);
        
    }
}

映射操做

好比map和flatMap方法,能夠把一個元素映射成另外一個元素。如下示例對比了 map 和 flatMap的區別,

package com.common.stream;

import java.util.Arrays;
import java.util.List;
import java.util.stream.Stream;

import static java.util.stream.Collectors.toList;

public class StreamMapOperateTest {

    public static void main(String[] args) {

        String[] arrayOfWords = {"Hello", "World", "Bye"};

        List<Stream<String>> res = Arrays.stream(arrayOfWords)
                // 把每一個單詞映射成一個數組,好比 "Hello" -> ['H','e','l','l','o']
                .map(word -> word.split(""))
                // 把每一個數組映射成一個Stream<String>, 好比 ['H','e','l','l','o'] -> Stream<String>
                // 效果是 [['H','e','l','l','o'],['W','o','r','l','d'],['B','y','e']] -> [Stream<String>,Stream<String>,Stream<String>]
                .map(Arrays::stream)
                .distinct()
                .collect(toList());

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


        /**
         * 要理解什麼是扁平化流,請考慮像[ [1,2,3],[4,5,6],[7,8,9] ]這樣的結構,它具備「兩個層次」。
         * 扁平化意味着將其轉變爲「一級」結構: [ 1,2,3,4,5,6,7,8,9 ] 。
         */
        List<String> resStr = Arrays.stream(arrayOfWords)
                .map(word -> word.split(""))
                // 效果是二維數組 到 一維數組
                // [['H','e','l','l','o'],['W','o','r','l','d'],['B','y','e']]  -> ['H','e','l','l','o','W','o','r','l','d','B','y','e']
                .flatMap(Arrays::stream)
                .peek(n -> {
                    System.out.println("after flatMap : " + n);
                })
                .distinct()
                .collect(toList());

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


    }
}

查找和匹配

package com.common.stream;

import java.util.Arrays;
import java.util.List;

public class StreamFindTest {

    private static final List<Dish> menu = Arrays.asList(
            new Dish("pork", 800, Dish.Type.MEAT),
            new Dish("beef", 700, Dish.Type.MEAT),
            new Dish("chicken", 400, Dish.Type.MEAT),
            new Dish("french fries", 530, Dish.Type.OTHER),
            new Dish("rice", 350, Dish.Type.OTHER),
            new Dish("season fruit", 120, Dish.Type.OTHER),
            new Dish("pizza", 550, Dish.Type.OTHER),
            new Dish("prawns", 300, Dish.Type.FISH),
            new Dish("salmon", 450, Dish.Type.FISH));

    public static void main(String[] args) {

        // anyMatch 操做
        if (menu.stream().anyMatch(x -> x.getCalories() > 500)) {
            System.out.println("anyMatch....");
        }

        // allMatch
        if (menu.stream().allMatch(x -> x.getCalories() < 1000)) {
            System.out.println("allMatch....");
        }

        // noneMatch
        if (menu.stream().noneMatch(x -> x.getCalories() >= 1000)) {
            System.out.println("noneMatch....");
        }

        // findAny 方法返回 Optional 對象
        // findAny方法將返回當前流中的任意元素。
        menu.stream().filter(x -> x.getCalories() > 500).findAny().ifPresent(System.out::println);

        // 找到第一個元素
        menu.stream().filter(x -> x.getCalories() > 500).findFirst().ifPresent(System.out::println);
    }
}

歸約操做

到目前爲止,終端操做都是返回一個boolean(allMatch之類的)、void (forEach)或Optional對象(findAny等),或者使用collect來將流中的全部元素組合成一個List。

歸約操做則是相似求和,將流歸約成一個值。 

以下,求全部菜譜的卡路里值,

package com.common.stream;

import java.util.Arrays;
import java.util.List;

public class StreamReduceTest {

    private static final List<Dish> menu = Arrays.asList(
            new Dish("pork", 800, Dish.Type.MEAT),
            new Dish("beef", 700, Dish.Type.MEAT),
            new Dish("chicken", 400, Dish.Type.MEAT),
            new Dish("french fries", 530, Dish.Type.OTHER),
            new Dish("rice", 350, Dish.Type.OTHER),
            new Dish("season fruit", 120, Dish.Type.OTHER),
            new Dish("pizza", 550, Dish.Type.OTHER),
            new Dish("prawns", 300, Dish.Type.FISH),
            new Dish("salmon", 450, Dish.Type.FISH));

    public static void main(String[] args) {

        // map reduce 操做
        int sum = menu.stream().map(Dish::getCalories).reduce(0, (a, b) -> a + b);

        System.out.println(sum);

    }
}

求最大最小值

// 求最大最小值
Optional<Integer> min = menu.stream().map(Dish::getCalories).reduce(Integer::min);

// 求最大最小值
Optional<Integer> max = menu.stream().map(Dish::getCalories).reduce(Integer::max);

if (min.isPresent()) {
    System.out.println(min.get());
}

if (max.isPresent()) {
    System.out.println(max.get());
}

=======END=======

相關文章
相關標籤/搜索