Java 8 (4) Stream 流 - 使用

在本節中將介紹Stream API支持的許多操做,這些操做能夠完成更復雜的數據查詢,如篩選、切片、映射、查找、匹配和歸約。還有一些特殊的流如:數值流、來自文件和數組等多種來源的流。java

 

篩選和切片數組

  1.用謂詞篩選緩存

    Streams接口支持filter方法,該操做會接受一個謂詞做爲參數,並返回一個包含全部符合謂詞的元素的流。例如篩選出全部素菜:dom

List<Dish> vegetarianMenu = menu.stream().filter(Dish::isVegetarian).collect(toList());

  2.篩選各異的元素函數

    流海支持一個叫作distinct的方法,它會返回一個元素各異(根據流所生成的元素的hashCode和equals方法的實現)的流。例如篩選全部的偶數並確保沒有重複的:工具

        List<Integer> nums = Arrays.asList(1,2,3,13,12,2,1,2,2,1,2,2,3,4,5);
        List<Integer> oddNums = nums.stream().filter(s->s%2==0).distinct().collect(toList());

  3.截斷流spa

    流支持limit(n)方法,該方法會返回一個不超過給定長度的流,所需的長度做爲參數傳遞給limit,若是流是有序的,則最多返回前n個元素。例如篩選熱量超過300卡路里的前3道菜:code

List<Dish> limit3 = menu.stream().filter(c->c.getCalories()>300).distinct().limit(3).collect(toList());

  4.跳過元素對象

    流還支持skip(n)方法,該方法會返回一個扔掉了前n個元素的流,若是流中元素U不足n個,則返回一個空流。例如:跳過超過300卡路里的頭兩道菜,並返回剩下的。blog

List<Dish> skip2 = menu.stream().filter(c->c.getCalories()>300).distinct().skip(2).collect(toList());

 

映射

  好比在SQL中,你能夠選擇從表中選擇一列,Stream API也經過map和flatMap方法提供了相似的工具。

  1.對流中每個元素應用函數

    流支持map方法,它接受一個函數做爲參數。這個函數會被應用到每一個元素上,並將其映射成一個新的元素。例以下面把Dish::getName傳給了map方法,來提取流中的菜名:

List<String> names = menu.stream().map(Dish::getName).collect(toList());

    由於getName返回一個String,因此map方法輸出的流的類型就是Stream<String>。例以下面把List<String> 映射爲List<Integer> 值是String的長度。

List<String> strs = Arrays.asList("lambda","action","java 8","stream");
List<Integer> ints = strs.stream().map(String::length).collect(toList());

    若是要找出每道菜的名稱有多長能夠再加上一個map:

List<Integer> nameLength = menu.stream().map(Dish::getName).map(String::length).collect(toList());

  2.流的扁平化

    經過下面這個例子介紹流的扁平化:給定字符串數組:["hello","world"],返回字符數組["h","e","l","o","w","r","d"]。

第1次嘗試:你可能以爲很容易,distinct一下就行了

List<String> words = Arrays.asList("hello","world");

List<String[]> collect = words.stream().map(w -> w.split("")).distinct().collect(toList());

可是,傳遞給map方法的lambda爲每一個單詞返回了一個Stirng[],所以map返回的流其實是Stream<String[]>類型的,而咱們想要的是Stream<String>類型的

第2次嘗試:map和Arrays.stream() 

首先要活的一個字符流,而不是字符串數組流,有一個叫作Arrays.stream()的方法能夠接收一個數組併產生一個流:例如

        String[] words = {"hello","world"};
        Stream<String> stream = Arrays.stream(words);

使用這個方法應用到前面的流水線裏看看

        List<Stream<String>> collect1 = words.stream()
                .map(w -> w.split(""))
                .map(Arrays::stream)
                .distinct()
                .collect(toList());

仍是不行,由於如今獲得的是一個流的列表List<Stream<String>>。

第3次嘗試:使用flatMap

        List<String> collect2 = words.stream()
                .map(w -> w.split(""))
                .flatMap(Arrays::stream)
                .distinct()
                .collect(toList());

使用flatMap的效果是,各個數組並非分別映射成一個流,而是映射成流的內容。全部使用map(Arrays::stream)時生成的單個流都被合併起來,即扁平化一個流。

映射練習:

  1.給定一個數字列表,返回每一個數的平方構成的列表:

List<Integer> numbers = Arrays.asList(1,2,3,56,78,9);
List<Integer> collect3 = numbers.stream().map(a -> a * a).collect(toList());

  2.給定兩個數字列表,返回全部的參數對。

        List<Integer> num1 = Arrays.asList(1, 2, 3, 4, 5);
        List<Integer> num2 = Arrays.asList(1, 2, 3, 4, 5);

        List<int[]> collect4 = num1.stream()
                .flatMap(i -> num2.stream().map(j -> new int[]{i, j}))
                .collect(toList());

  3.擴展前一個例子,只返回總和能夠被3整除的。

        List<int[]> collect5 = num1.stream()
                .flatMap(i -> num2.stream().filter(j -> (i + j) % 3 == 0).map(j -> new int[]{i, j}))
                .collect(toList());

 

查找和匹配

  查看數據集中的某些元素是否匹配一個給定的屬性,Stream API經過allMatch、anyMatch、noneMatch、findFirst和findAny方法提供了這樣的工具

  1.至少匹配一個元素 anyMatch

        if(menu.stream().anyMatch(m->m.getCalories()>400)){
            System.out.println("有卡路里大於400的食物");
        }

  2.匹配全部元素 allMatch

        if(menu.stream().allMatch(Dish::isVegetarian)){
            System.out.println("全部菜都是素菜");
        }

  3.沒有匹配 noneMatch

        if(menu.stream().noneMatch(m->m.getCalories() < 100)){
            System.out.println("全部菜都不小於100卡路里");
        }

anyMatch、allMatch、noneMatch這三個操做都用到了短路,就是Java中的 && || 運算符短路在流中的版本。

短路:有些操做不須要處理整個流就能夠獲得結果。例如一個用and鏈接起來的大布爾表達式,無論表達式有多長,只要找到一個爲false就推斷整個表達式爲false。

對於流而言,某些操做(allMatch、anyMatch、noneMatch、findFirst、findAny)不用處理整個流就能夠獲得結果,limit也是一個短路操做。

 

查找元素

  findAndy方法返回當前流中的任意元素:

Optional<Dish> any = menu.stream().filter(Dish::isVegetarian).findAny();

Optional<T>類是一個容器類,表明一個值存在或不存在。例如這個例子,可能會什麼都沒找到。

  isPresent():optional存在值時返回true,不然返回false。

  ifPresent(Consumer<T> block)會在值存在的時候執行給定的代碼塊,(Consumer接口:傳遞一個T參數,消費這個T 什麼也不返回)。

  T get():值存在時返回值,不然拋出一個NoSuchElement異常。

  T orElse(T other):會在值存在時返回值,不然返回一個默認值。

例如:若是找到了輸出這個名字,不然什麼也不錯

menu.stream().filter(Dish::isVegetarian).findAny().ifPresent(d->System.out.println(d.getName()));

  findFrist方法返回第一個元素:

menu.stream().filter(Dish::isVegetarian).findFirst().ifPresent(d->System.out.println(d.getName()));

findFrist和findAny:找到第一個元素在並行上限制更多,若是不關心返回的元素是哪一個就是用findAny,由於它在是用並行流時限制較少。

 

歸約 reduce

  例如查找全部菜的總卡路里,或菜中最高的卡路里是哪一個,這類查詢須要將流中全部的元素反覆結合起來,獲得一個值。這樣的查詢能夠被歸類爲歸約操做。

  求和:

在是用reduce方法以前,來看看for-each循環來對數字列表中的元素求和:

        List<Integer> nums1 = Arrays.asList(1,2,3,4,5);

        int sum  = 0;
        for(int i : nums1){
            sum +=i;
        }

是用reduce來求和以下:

int reduceSum = nums1.stream().reduce(0, (a, b) -> a + b);

這裏的第一個參數 就如上面設置的初始值同樣,第二個參數就是一個BinaryOperator<T>來將兩個元素結合起來產生一個新值。

在Java 8 中,Integer類有一個現有的靜態sum方法來對兩個數求和,所以能夠改寫成:

Integer reduce = nums1.stream().reduce(0, Integer::sum);

reduce還有一個重載版本,它不用接受初始值,但會返回一個Optional對象:

Optional<Integer> reduce = nums1.stream().reduce(Integer::sum);

  最大值和最小值:

Optional<Integer> max = nums1.stream().reduce(Integer::max);
Optional<Integer> min = nums1.stream().reduce(Integer::min);

固然也能夠寫成 (x,y) -> x< y ? x : y;而不是Integer::min,不事後者更好讀。

流操做:無狀態和有狀態

  map或feilter等操做會從輸入流中獲取每個元素,並在輸出流獲得0或1個結果。這些操做通常都是無狀態的:他們沒有內部狀態。

  但reduce、sum、max等操做須要內部狀態類累計結果,無論流中又多少元素要處理,內部狀態都是有界的。

  相反,sort或distinct等操做一開始都和filter、map差很少--都是接受一個流,再生成一個流(中間操做),但有一個關鍵的區別。從流中排序和刪除重複項時都須要知道先前的歷史,咱們把這些操做叫作有狀態操做。

到目前學到的流的方法以下:

  中間操做:

    filter、distinct、skip、limit、map、flatMap、sorted。

  終端操做:

    anyMatch、noneMatch、allMatch、findAny、findFirst、forEach、collect、reduce、count。

小練習:

public class Trader {
    private final String name;
    private final String city;
}
public class Transaction {
    private final Trader trader;
    private final int year;
    private final int value;
}
        Trader raoul = new Trader("Raoul", "Cambridge");
        Trader mario = new Trader("Mario","Milan");
        Trader alan = new Trader("Alan","Cambridge");
        Trader brian = new Trader("Brian","Cambridge");
        List<Transaction> transactions = Arrays.asList(
                new Transaction(brian, 2011, 300),
                new Transaction(raoul, 2012, 1000),
                new Transaction(raoul, 2011, 400),
                new Transaction(mario, 2012, 710),
                new Transaction(mario, 2012, 700),
                new Transaction(alan, 2012, 950)
        );
        //1.找出2011年發生的全部交易,並按交易額排序(從低到高)
        List<Transaction> collect = transactions.stream()
                .filter(t -> t.getYear() == 2011)
                .sorted(Comparator.comparing(Transaction::getValue))
                .collect(toList());
        System.out.println(collect);
        //2.交易員都在哪些不一樣的城市工做過?
        List<String> collect1 = transactions.stream()
                .map(m -> m.getTrader().getCity())
                .distinct()
                .collect(toList());
        System.out.println(collect1);
        //3.查找全部來自於劍橋的交易員,並按姓名排序。
        List<Trader> collect2 = transactions.stream()
                .map(m -> m.getTrader())
                .filter(m -> m.getCity() == "Cambridge")
                .distinct()
                .sorted(Comparator.comparing(Trader::getName))
                .collect(toList());
        //4.返回全部交易員的姓名字符串,按字母順序排序
        String reduce = transactions.stream()
                .map(t -> t.getTrader().getName())
                .distinct()
                .sorted()
                .reduce("", (n1, n2) -> n1 + n2);//效率不高是stirng 拼接 下一節joining
        //5.有沒有交易員是在米蘭工做的
        boolean milan = transactions.stream().anyMatch(c -> c.getTrader().getCity().equals("Milan"));
        //6.打印生活在劍橋的交易員的全部交易額
        transactions.stream()
                .filter(t->t.getTrader().getCity().equals("Cambridge"))
                .map(Transaction::getValue)
                .forEach(System.out::println);
        //7.全部交易中,最高的交易額是多少
        Optional<Integer> reduce1 = transactions.stream()
                .map(Transaction::getValue)
                .reduce(Integer::max);
        //8.找到交易額最小的交易
        Optional<Transaction> reduce2 = transactions.stream()
                .reduce((t1, t2) -> t1.getValue() < t2.getValue() ? t1 : t2);
        //流還支持min和max方法
        Optional<Transaction> smallTransaction = transactions.stream()
                .min(Comparator.comparing(Transaction::getValue));
        smallTransaction.ifPresent(System.out::println);

 

數值流

  前面使用reduce方法計算了元素的總和,例如:

Integer reduce3 = transactions.stream().map(c -> c.getValue()).reduce(0, Integer::sum);

這段代碼的問題是,它有一個暗含的裝箱成本,每一個Integer都必須拆箱成一個原始類型後再進行求和,要是能夠像下面這樣直接調用sum方法不是更好?

int sum3 = transactions.stream().map(c->c.getValue()).sum();

這是不可能的,由於map方法會生成一個Stream<T>,雖然流中的元素是Integer類型,但Streams接口沒有定義sum方法。Stream API提供了原始類型流特化,專門支持處理數值流的方法。

原始類型流特化

  Java 8 引入了三個原始類型特化流接口來解決這個問題:IntStream、DoubleStream、LongStream,分別將流中的元素特化爲int、long和double,從而避免了暗含的裝箱成本。

  1.映射到數值流

    將流轉換爲特化版本的經常使用方法是mapToInt、mapToDouble和mapToLong,這些方法返回一個特化流,而不是Stream<T>。例如:

int sum = transactions.stream().mapToInt(Transaction::getValue).sum();

mapToInt返回一個IntStream而不是Stream<Integer>,而後就能夠調用IntStream中的sum方法,若是流是空的,sum默認返回0.還支持其餘方法如max、min、average等。

  2.轉換回對象流

    將特化流轉回非特化流,可使用boxed方法:

        IntStream intStream = transactions.stream().mapToInt(Transaction::getValue);
        Stream<Integer> stream = intStream.boxed();

  3.默認值OptionalInt

    Optional對於三中原始流特化,也分別有一個Optional原始類型特化版本:OptionalInt、OptionalDouble、OptionalLong。例如要找到最大元素:

OptionalInt max = transactions.stream().mapToInt(Transaction::getValue).max();

若是沒有最大值能夠給一個默認值:

int i = transactions.stream().mapToInt(Transaction::getValue).max().orElse(1);

 

數值範圍

  在Java 8中引入了兩個能夠用於IntStream和LongStream的靜態方法,幫助生成這種1到100之間數字的範圍:range和rangeClosed。這兩個方法第一個參數 起始值,第二個參數結束值。但range是不包含結束值的,而rangeClosed則包含結束值。就是< 和<=的區別。

        IntStream evenNumbers = IntStream.rangeClosed(1,100).filter(i->i%2==0);
        System.out.println(evenNumbers.count());//50

若是是range方法 則只有49個結果 由於它不包含最後100這個數字。

range示例:取出1到100之間的勾股數:

        Stream<double[]> stream1 = IntStream.rangeClosed(1, 100)
                .boxed()
                .flatMap(a -> IntStream.rangeClosed(a, 100)
                        .mapToObj(
                                b -> new double[]{a, b, Math.sqrt(a * a + b * b)}
                        ).filter(t -> t[2] % 1 == 0));
        stream1.limit(3).forEach(t -> System.out.println(t[0] + "," + t[1] + "," + t[2]));

 

構建流

  1.由值建立流

    Stream.of方法能夠顯示的建立一個流,它能夠接受任意數量的參數。例如:建立一個字符串流,將字符串轉換爲大寫,再打印出來:

        Stream<String> stringStream = Stream.of("Java 8", "Lambdas", "in", "Action");
        stringStream.map(String::toUpperCase).forEach(System.out::println);

    還可使用mepty獲得一個空流:

Stream<String> emptyString = Stream.empty();

  2.由數組建立流

    Arrays.Stream能夠從數組建立一個流,它接受一個數組做爲參數,例如你能夠講一個原始類型int的數組轉換成一個IntStream:

        int[] nums4 = {2,3,5,56,6,4,4,45,234,2};
        IntStream stream2 = Arrays.stream(nums4);

  3.由文件生成流

    Files.lines能夠從文件獲得一個流,其中的每一個元素都是該文件的一行。

        Stream<String> lines = Files.lines(Paths.get("/Users/baidawei/Desktop/test.txt"), Charset.defaultCharset());
        lines.forEach(c->System.out.println(c.toString()));

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

    Stream.iterate和Stream.generate這兩個靜態方法能夠建立所謂的無限流:不像從固定集合建立的流那樣有固定大小的流。由這兩個產生的流會用給定的函數按需建立值,所以能夠無窮的計算下去,通常來講應該使用limit來對這種流加以限制。

    4.1 迭代

        Stream.iterate(0,n->n+2)
                .limit(10)
                .forEach(System.out::println);

      iterate 第一個參數是起始值,第二個參數是一個lambda表達式(UnaryOperator<T>)類型的,沒有終止條件,按需計算。因此須要limit截斷

    4.2 生成

      與iterate相似,generate也可讓你按需生成一個無限流。但generate不是依次對每一個新生成的值應用函數的。它接受一個Supplier<T>類型的Lambda提供新的值:

       Stream.generate(Math::random)
                .limit(5)
                .forEach(System.out::println);

      咱們使用的供應源(Math::radom)是無狀態的:它不會在任何地方記錄任何值。

 

小結:

  1. 、篩選和切片:filter、distinct、skip、limit。

  二、映射:map、flatMap。

  三、查找:findFirst、findAny。

  四、匹配:allMatch、anyMatch、noneMatch。

  五、這些方法都利用了短路:找到結果就當即中止計算,沒有必要處理整個流。

  六、歸約:reduce、聚合 計算最大 最小值。

  七、filter和map等是無狀態的,他們並不存儲任何狀態。reduce等操做須要存儲狀態才能計算一個值。sorted和distinct等操做也要存儲狀態,由於他們須要把六中的全部元素緩存起來才能返回一個新的流。這種操做稱爲有狀態操做。

  八、流油三種基本的原始類型特化:IntStream、DoubleStream和LongStream。

  九、流不盡能夠從集合建立,也能夠從值、數組、文件以及iterate與generate等方法建立。

  十、無限流是沒有固定大小的流。

相關文章
相關標籤/搜索