JAVA8學習——從使用角度深刻Stream流(學習過程)

Stream 流

初識Stream流

簡單認識一下Stream:Stream類中的官方介紹:html

/**
 * A sequence of elements supporting sequential and parallel aggregate
 * operations.  The following example illustrates an aggregate operation using
 * {@link Stream} and {@link IntStream}:
 *
 * <pre>{@code
 *     int sum = widgets.stream()
 *                      .filter(w -> w.getColor() == RED)
 *                      .mapToInt(w -> w.getWeight())
 *                      .sum();
 * }</pre>
 * In this example, {@code widgets} is a {@code Collection<Widget>}.  We create
 * a stream of {@code Widget} objects via {@link Collection#stream Collection.stream()},
 * filter it to produce a stream containing only the red widgets, and then
 * transform it into a stream of {@code int} values representing the weight of
 * each red widget. Then this stream is summed to produce a total weight.
 *
 */

看這麼一個案例,相似於js中的鏈式操做。就明白了大概流是什麼樣子的。相似於 Linux的 pipelinejava

流包含三部分組成:

  1. 零個或多箇中間操做
  2. 終止操做

流操做的分類:

  1. 惰性求值
  2. 及早求值
stream.xxx().yyy().zzz().count();

中間操做:惰性求值。只有在count()被調用的時候,中間的操做纔會進行求值。sql

及早求值,count()方法調用的時候馬上求值,這就叫作及早求值。express

流中的及早求值只會有一個。編程

生成流的三種方式

public class StreamTest {
    public static void main(String[] args) {
        //本章才正式的開始對流進行講解。

        //第一種方式,經過of方法
        Stream stream1 = Stream.of("hello","world");
        //第二種方式,經過數組方式
        String[] strings = new String[]{"hello","world"};
        Stream stream2 = Arrays.stream(strings);
        Stream stream3 = Stream.of(strings); //of的底層就是 經過Arrays.stream()來實現的.
        //第三種方式,經過集合.stream
        List<String> list = Arrays.asList("hello", "world");
        list.stream();

    }
}

流怎麼用(入門使用)

用法一:api

public class streamTest2 {
    public static void main(String[] args) {
        //Intstream 怎麼用
        IntStream.of(5, 6, 7).forEach(System.out::println);
        System.out.println("----");

        IntStream.range(3, 8).forEach(System.out::println);
        System.out.println("----");

        IntStream.rangeClosed(3, 8).forEach(System.out::println);
        System.out.println("----");
    }
}
public class streamTest3 {
    public static void main(String[] args) {
        //List類型,int的值, 對每個元素*2,而後加起來,獲得結果
        List<Integer> list = Arrays.asList(1, 2, 3, 4, 5, 6);
        //之前的寫法
//        int i=0;
//        for (Integer i : list) {
//            sum += 2;
//        }
//        sum...

        //stream的寫法,一行
        System.out.println(list.stream().map(integer -> integer*2).reduce(0,Integer::sum));

        //reduce方法,map()方法的調用會在下面進行詳解.
        //實現簡單,語義更明確
    }
}

reduce(),終止操做,及早求值.數組

深刻stream流

函數式編程,最根本的一點:方法傳遞的是行爲.之前傳遞的都是數據.markdown

  1. Collection提供了新的stream()方法
  2. **流不存儲值,經過管道的方式獲取值
  3. 本質是函數式的,對流的操做會生成一個結果,不過並不會修改底層的數據源,集合能夠做爲流的底層數據源
  4. 延遲查找,不少流操做(過濾,映射,排序等)均可以延遲實現(lazy)

看這個Example:app

public class streamTest4 {
    public static void main(String[] args) {
        Stream<String> stream = Stream.of("hello", "world", "hello world");
      //lambda寫法   
      //stream.toArray(length -> new String[length]);
      //方法引用的寫法 (構造方法引用)
        String[] stringArray = stream.toArray(String[]::new);
        Arrays.asList(stringArray).forEach(System.out::println);
    }
}

已知流,轉List框架

//已知流,轉List
        Stream<String> stream = Stream.of("hello", "world", "hello world");
        List<String> collect = stream.collect(Collectors.toList());
        collect.forEach(System.out::println);

collect()方法詳解 - Collectors裏面也是經過collect(三個參數)這個方法來實現的

/**
     * 第一個參數介紹
     * Performs a <a href="package-summary.html#MutableReduction">mutable
     * reduction</a> operation on the elements of this stream.  A mutable
     * reduction is one in which the reduced value is a mutable result container,
     * such as an {@code ArrayList}, and elements are incorporated by updating
     * the state of the result rather than by replacing the result.  This
     * produces a result equivalent to:  第二個參數的介紹
     * <pre>{@code
     *     R result = supplier.get();
     *     for (T element : this stream)
     *         accumulator.accept(result, element);
     *     return result;
     * }</pre>
     * 被並行化. 流帶來的好處.
     * <p>Like {@link #reduce(Object, BinaryOperator)}, {@code collect} operations
     * can be parallelized without requiring additional synchronization.
     * 這是一個終止操做.
     * <p>This is a <a href="package-summary.html#StreamOps">terminal
     * operation</a>.
     * 方法簽名是很是適合於使用方法引用的方式.就是最下面舉例的Example
     * @apiNote There are many existing classes in the JDK whose signatures are
     * well-suited for use with method references as arguments to {@code collect()}.
     * For example, the following will accumulate strings into an {@code ArrayList}:
     * <pre>{@code
     *     List<String> asList = stringStream.collect(ArrayList::new, ArrayList::add,
     *                                                ArrayList::addAll);
     * }</pre>
     * 擴展功能:字符串實現拼接的操做
     * <p>The following will take a stream of strings and concatenates them into a
     * single string:
     * <pre>{@code
     *     String concat = stringStream.collect(StringBuilder::new, StringBuilder::append,
     *                                          StringBuilder::append)
     *                                 .toString();
     * }</pre>
     *
     * @param <R> type of the result
     第一個參數:結果容器,如LinkList
     * @param supplier a function that creates a new result container. For a
     *                 parallel execution, this function may be called
     *                 multiple times and must return a fresh value each time.
     第二個參數:關聯性的,不衝突的,無狀態的,用於合併.   item->list
     * @param accumulator an <a href="package-summary.html#Associativity">associative</a>,
     *                    <a href="package-summary.html#NonInterference">non-interfering</a>,
     *                    <a href="package-summary.html#Statelessness">stateless</a>
     *                    function for incorporating an additional element into a result
     第三個參數:用於融合,將上次遍歷獲得的集合融合到最終的結果集中.
     * @param combiner an <a href="package-summary.html#Associativity">associative</a>,
     *                    <a href="package-summary.html#NonInterference">non-interfering</a>,
     *                    <a href="package-summary.html#Statelessness">stateless</a>
     *                    function for combining two values, which must be
     *                    compatible with the accumulator function
     * @return the result of the reduction
     */
    <R> R collect(Supplier<R> supplier,
                  BiConsumer<R, ? super T> accumulator,
                  BiConsumer<R, R> combiner);

經過源碼解釋:咱們得知實現流轉List的底層就是經過這個三參數的collect方法來實現的,咱們逐一來對這三個參數進行了解.

1.參數1:supplier,類型Supplier的函數式接口. 功能:用來提供一個初步的List容器

2.參數2: accumulator,類型BiConsumer的函數式接口. 功能:累加器,將流中的一個個元素累加進集合中.

3.參數3:combiner,類型爲BiConsumer的函數式接口. 功能:組合器,將上一次遍歷獲得一個的集合進行融合到最終的List中.

自行閱讀上面的Collector的文檔.我說的這些內容都在裏面有所體現.

經過上述的瞭解,咱們能夠經過三參數的collect()方法,來本身實現一個底層stream轉換List的實現,以下:

//功能描述:已知流,轉List
Stream<String> stream = Stream.of("hello", "world", "hello world");
List<String> collect = stream.collect(Collectors.toList());
collect.forEach(System.out::println);

//使用collect(三個參數)的底層方法來實現這個操做.  由於這個三參的collect()方法就是這個操做的底層.
List<String> list = stream.collect(() -> new ArrayList(),(theList,item)->theList.add(item),(theList1,theList2)->theList1.addAll(theList2));

//經過方法引用優化後的代碼以下:
//優化後的代碼:
List<String> list1 = stream.collect(LinkedList::new,LinkedList::add,LinkedList::addAll);

上述源碼註釋中還提供了 字符串拼接的操做

* 擴展功能:字符串實現拼接的操做
     * <p>The following will take a stream of strings and concatenates them into a
     * single string:
     * <pre>{@code
     *     String concat = stringStream.collect(StringBuilder::new, StringBuilder::append,
     *                                          StringBuilder::append)
     *                                 .toString();
     * }</pre>
     *

流的其餘使用

//使用 Collectors.toCollection()方法來實現 流轉List
        Stream<String> stream = Stream.of("hello", "world", "hello world");
//        ArrayList<String> list = stream.collect(Collectors.toCollection(ArrayList::new));
//        list.forEach(System.out::println);

        //使用 Collectors.toCollection()方法來實現 流轉Set
        Set<String> list = stream.collect(Collectors.toCollection(TreeSet::new));
        list.forEach(System.out::println);

        //使用 方法來實現,流轉String字符串
        stream.collect(Collectors.joining());

之後開發的時候,要多考慮,List,Set,這些轉換是否可使用JAVA8提供的這些stream來實現.用到實際開發中.

再思考

public class StreamTest5 {
    public static void main(String[] args) {
        //集合,所有轉換大寫,而後輸出.
        List<String> list = Arrays.asList("hello", "world", "hello world");
        //要考慮能不能用函數式接口,lambda表達式的技能?顯然是能夠呢
        //這是否是映射? 先要要用map. 給定一個參數,返回一個結果.
        //java8提供這些接口,就是爲了方便開發者.合理的應用.
        list.stream().map(String::toUpperCase).collect(Collectors.toList()).forEach(System.out::println);
      
        //求出每一個數字的平方,而後打印出來
        List<Integer> list1 = Arrays.asList(1, 2, 3, 4, 5);
        list1.stream().map(item -> item * item).collect(Collectors.toList()).forEach(System.out::println);
      
    }
}
//要考慮能不能用函數式接口,lambda表達式的技能?顯然是能夠呢
    //這是否是映射? 先要要用map. 給定一個參數,返回一個結果.
    //java8提供這些接口,就是爲了方便開發者.合理的應用.

flatMap()方法:扁平化映射

/**
     * Returns a stream consisting of the results of replacing each element of
     * this stream with the contents of a mapped stream produced by applying
     * the provided mapping function to each element.  Each mapped stream is
     * {@link java.util.stream.BaseStream#close() closed} after its contents
     * have been placed into this stream.  (If a mapped stream is {@code null}
     * an empty stream is used, instead.)
     *
     * <p>This is an <a href="package-summary.html#StreamOps">intermediate
     * operation</a>.
     *
     * @apiNote
     * The {@code flatMap()} operation has the effect of applying a one-to-many
     * transformation to the elements of the stream, and then flattening the
     * resulting elements into a new stream.
     *
     * <p><b>Examples.</b>
     *
     * <p>If {@code orders} is a stream of purchase orders, and each purchase
     * order contains a collection of line items, then the following produces a
     * stream containing all the line items in all the orders:
     * <pre>{@code
     *     orders.flatMap(order -> order.getLineItems().stream())...
     * }</pre>
     *
     * <p>If {@code path} is the path to a file, then the following produces a
     * stream of the {@code words} contained in that file:
     * <pre>{@code
     *     Stream<String> lines = Files.lines(path, StandardCharsets.UTF_8);
     *     Stream<String> words = lines.flatMap(line -> Stream.of(line.split(" +")));
     * }</pre>
     * The {@code mapper} function passed to {@code flatMap} splits a line,
     * using a simple regular expression, into an array of words, and then
     * creates a stream of words from that array.
     *
     * @param <R> The element type of the new stream
     * @param mapper a <a href="package-summary.html#NonInterference">non-interfering</a>,
     *               <a href="package-summary.html#Statelessness">stateless</a>
     *               function to apply to each element which produces a stream
     *               of new values
     * @return the new stream
     */
    <R> Stream<R> flatMap(Function<? super T, ? extends Stream<? extends R>> mapper);

和map很像,可是徹底不一樣.不然就不會存在這個方法了.

扁平化的map;

1.map中映射的時候, 一個集合有三個List,每一個List又有不一樣的值.映射完以後,模塊還在

2.flatMap中映射的時候,一個集合有三個List, 打平的去給融合的到一個list中.

實例Example:

//每個元素都乘方,而後將數據做爲一個總體,輸出. 當作一個集合.  就要用flatmap()
        Stream<List<Integer>> listStream = Stream.of(Arrays.asList(1), Arrays.asList(2, 3), Arrays.asList(4, 5, 6));
        listStream.flatMap(theList->theList.stream()).map(integer -> integer*integer).forEach(System.out::println);

Stream類中的其餘方法介紹

  1. generate()
/**
     * Returns an infinite sequential unordered stream where each element is
     * generated by the provided {@code Supplier}.  This is suitable for
     * generating constant streams, streams of random elements, etc.
     *
     * @param <T> the type of stream elements
     * @param s the {@code Supplier} of generated elements
     * @return a new infinite sequential unordered {@code Stream}
     */
    public static<T> Stream<T> generate(Supplier<T> s) {
        Objects.requireNonNull(s);
        return StreamSupport.stream(
                new StreamSpliterators.InfiniteSupplyingSpliterator.OfRef<>(Long.MAX_VALUE, s), false);
    }

如何使用?以下Example:

public class StreamTest6 {
    public static void main(String[] args) {
        Stream<String> generate = Stream.generate(UUID.randomUUID()::toString);
        System.out.println(generate.findFirst());
    }
}
Optional<T> findFirst();

爲何這個findFirst()方法會返回一個Optional?

由於Optional,就是爲了規避NPE的問題.

因此此處須要使用Optional.ifPresent(),這纔是Optional類的正確使用方法.應該修改成:

public class StreamTest6 {
    public static void main(String[] args) {
        Stream<String> generate = Stream.generate(UUID.randomUUID()::toString);
       generate.findFirst().ifPresent(System.out::println);
    }
}
  1. iterate()
public static<T> Stream<T> iterate(final T seed, final UnaryOperator<T> f) {
        Objects.requireNonNull(f);
        final Iterator<T> iterator = new Iterator<T>() {
            @SuppressWarnings("unchecked")
            T t = (T) Streams.NONE;

            @Override
            public boolean hasNext() {
                return true;
            }

            @Override
            public T next() {
                return t = (t == Streams.NONE) ? seed : f.apply(t);
            }
        };
        return StreamSupport.stream(Spliterators.spliteratorUnknownSize(
                iterator,
                Spliterator.ORDERED | Spliterator.IMMUTABLE), false);
    }

如何使用?

package com.dawa.jdk8.stream;

import java.util.UUID;
import java.util.stream.Stream;

public class StreamTest6 {
    public static void main(String[] args) {
        Stream<String> generate = Stream.generate(UUID.randomUUID()::toString);
       generate.findFirst().ifPresent(System.out::println);

        //若是不加限制,iterate 會變成一個無限流.
        //Stream.iterate(1, integer -> integer + 2).forEach(System.out::println);
        //因此在使用的時候必定不要單獨使用.
        //要搭配limit()方法,一箇中間操做,使用.
        Stream.iterate(1, integer -> integer + 2).limit(6).forEach(System.out::println);
    }
}

注意: //若是不加限制,iterate 會變成一個無限流.
//因此在使用的時候必定不要單獨使用.
//要搭配limit()方法,一箇中間操做,使用.

找出(1,3,5,7,9)流中大於2的元素,而後將每一個元素乘以2,而後忽略流中的前兩個元素,而後再取出流的前兩個元素,最後求出流中元素的總和.

//找出(1,3,5,7,9)流中大於2的元素,而後將每一個元素乘以2,而後忽略流中的前兩個元素,而後再取出流的前兩個元素,最後求出流中元素的總和.
// Stream<Integer> stream = Stream.of(1, 3, 5, 7, 9);
        Stream<Integer> stream = Stream.iterate(1, integer -> integer + 2).limit(6);//經過iterate方法來獲取值
        System.out.println(stream.filter(integer -> integer > 2).mapToInt(integer -> integer * 2).skip(2).limit(2).sum());
//用到的方法. map,mapToint,skip,limit.
  1. ...skip() 跳過

  2. ...limit() 截取

  3. ...map().mapToInt(),mapToDouble().... 映射

    mapToInt... 避免自動裝箱和自動拆箱.(避免性能損耗).

  4. ...sum(),min(),max(). 最大,最小,求和等等

    ​ sum()返回值類型是int.

    ​ min().max(),返回值類型是:OptionalInt.

    爲何呢?Optional類,由於使用與否,本質是取決於,這個值可不可能爲空.

  5. summaryStatistics():小結,總結.流中的數據的簡單統計.

    如:一個小結對象:IntSummaryStatistics{count=2, sum=32, min=14, average=16.000000, max=18}

    這個類提供了各樣的方法.

    getCount
    getSum
    getMin
    getMax
    getAverage
    toString

上面的案例裏面,已經使用了Stream類中的大量的方法.若有須要,自行查詢官方源碼.

注意:在對流進行中間操做的時候,會返回一個全新的流.直到進行一個終止操做的時候,纔會獲得最終的結果.

關於流被關閉的問題.

image-20200104174241664

剛纔無心之間,在操做的時候,拋出來一個這樣的異常,提示流已經被關閉.

覆盤一下代碼:

Stream<Integer> stream = Stream.iterate(1, integer -> integer + 2).limit(6);
System.out.println(stream);
System.out.println(stream.filter(integer -> integer > 2));
System.out.println(stream.distinct());

流的特色

  1. 一旦被操做了.就會自動關閉流.
  2. 同一個流不能被重複操做.
  3. 流關閉了就不能繼續操做了.
  4. 每一次中間操做都會返回一個新的操做流對象.

如何規避?

  1. 先生成一個流,操做完以後,再生成一個流.

  2. 緊接着去操做新生成的流.

  3. Stream<Integer> stream = Stream.iterate(1, integer -> integer + 2).limit(6);//經過iterate方法來獲取值
    
    System.out.println(stream);
    Stream<Integer> stream1 = stream.filter(integer -> integer > 2);
    System.out.println(stream1);
    Stream<Integer> stream2 = stream1.distinct();
    System.out.println(stream2);

關於中間操做和終止操做之間的本質區別

Example:

public class StreamTest7 {
    public static void main(String[] args) {
        List<String> list = Arrays.asList("hello", "world", "hello world");
        //首字母大寫,其餘的小寫,而後打印輸出.
        list.stream().map(item -> item.substring(0, 1).toUpperCase() + item.substring(1)).forEach(System.out::println);

        //另外的操做
        list.stream().map(item -> {
            String result = item.substring(0, 1).toUpperCase() + item.substring(1);
            System.out.println("test");
            return result;
        });//運行以後沒有值

        //另外的操做
        list.stream().map(item -> {
            String result = item.substring(0, 1).toUpperCase() + item.substring(1);
            System.out.println("test");
            return result;
        }).forEach(System.out::println);//運行以後可以執行.

        //緣由:中間操做,若是沒有終止操做,是不會本身執行的,是lazy類型的.是惰性求值的.

    }
}

緣由:中間操做,若是沒有終止操做,是不會本身執行的,是lazy類型的.是惰性求值的.

再考慮一下效率問題.

也許可能會認爲,屢次中間操做,會屢次循環,會下降效率.

其實只執行了一次.並不會影響效率.

會有一個容器,把全部的中間操做放在一塊兒.一次執行.並不會有冗餘操做.

如何區分中間操做和終止操做

中間操做都會返回一個Stream對象,好比說返回Stream ,...等

再看另一個操做:關於中間操做和終止操做的影響

public class StreamTest8 {
    public static void main(String[] args) {
        IntStream.iterate(0, i -> (i + 1) % 2).distinct().limit(6).forEach(System.out::println);
    }
}

上述代碼跑起來以後是不會自動終止的.

應該修改成:

public class StreamTest8 {
    public static void main(String[] args) {
        IntStream.iterate(0, i -> (i + 1) % 2).limit(6).distinct().forEach(System.out::println);
    }
}

這個緣由就是由於中間操做和終止操做的影響.

  1. 若是先執行limit,就是一個終止操做.而後再消除重複一次,程序就會終止.

  2. 若是先執行消除重複操做,就是第一種狀況,返回一個流,再截取6個,流並無關閉.


Stream底層深刻

  • 和迭代器又不一樣的是,Stream能夠並行化操做,迭代器只能命令式地,串行化操做
  • 當使用串行方式去遍歷時,每一個item讀完後再讀下一個item.
  • 使用並行去遍歷時,數據會被分紅多個段,其中每一個都在不一樣的線程中去處理,而後將結果一塊兒輸出
  • Stream流的並行操做依賴於JAVA7中引入的Fork/Join框架.

流的本質三個主要操做:源->中間操做->中間操做->...->終止操做

這裏咱們藉助一個SQL來進行參照學習

select name from student where age>20 and address='beijing' order by age desc;

描述性的語言

經過stream把這個SQL描述出來

student.stream()
  .filter(student->student.getAge>20)
  .filter(student->student.getAddress()
          .equals("beijing")
          .sorted(...)
          .forEach(student->System.out.println(student.getName()));
//這個描述和上面的SQL實際上是等價的.

你只是給DB發送了一個指令,而沒有所怎麼去找.你只是給出了一個描述,而後根據降序之類的規則去進行篩選.對於整個過程,你徹底沒有告訴底層去怎麼實現.

SQL是這樣,Stream也是這樣.只是描述性的語言.

這種方式就叫作內部迭代.

內部迭代

外部迭代(以前的方式)

  1. 不是描述性的處理方式,徹底基於老版本的實現方式.和描述性語言相比,這個可讀性太差.

  2. 都是串行化的操做,不能並行化

for(int i=0;i<student.size();i++){
  Student student = students.get(i);
  if(student.getAge()>20&&student.getAddress().equals("beijing")){
    list.add(student);
  }
}
//而後進行排序
Collection.sort(list,Comparator()...);
//而後再去尋找須要的東西
for(Student student:list){
    System.out.println(student.getName);
}

內部迭代(描述性語言)

新人也是能看懂的吧.

student.stream()
  .filter(student->student.getAge>20)
  .filter(student->student.getAddress()
          .equals("beijing")
          .sorted(...)
          .forEach(student->System.out.println(student.getName()));

Stream的出現和集合是密不可分的.

  1. 能夠執行並行化的操做.
  2. 底層執行的時候,不是一個條件一個條件的去循環遍歷的

內部迭代和外部迭代最本質的區別

  1. 耦合度
  2. 底層處理方式
  3. 並行和串行化的不一樣

總結

  1. 集合關注的是數據與數據存儲自己;
  2. 流關注的則是對數據的計算;
  3. 流與迭代器相似的一點是:流是沒法重複使用和消費的;
  4. 底層使用:fork/join 方法,分解大任務爲小任務去執行.

最須要注意的

流的執行原理必定不是一個方法一個方法的執行循環遍歷的.


並行流的使用(parallelStream)

串行流(stream())和並行流(parallelStream())的執行效率判斷.

  • 串行流
package com.dawa.jdk8.stream;

import java.util.ArrayList;
import java.util.UUID;
import java.util.concurrent.TimeUnit;

public class StreamTest9 {
    public static void main(String[] args) {
        ArrayList<Object> list = new ArrayList<>(5000000);
        for (int i = 0; i < 5000000; i++) {
            list.add(UUID.randomUUID().toString());
        }

        System.out.println("開始排序");
        long startTime = System.nanoTime();//納秒 比毫秒的精度高
        list.stream().sorted().count();

        long endTime = System.nanoTime(); //納秒, 結束時間

        long millis = TimeUnit.NANOSECONDS.toMillis(endTime - startTime);
        System.out.println("耗時:" + millis + "毫秒");
    }
}

運行結果: 串行耗時:4.0秒

image-20200104185911432

  • 並行流
public class StreamTest9 {
    public static void main(String[] args) {
        ArrayList<Object> list = new ArrayList<>(5000000);
        for (int i = 0; i < 5000000; i++) {
            list.add(UUID.randomUUID().toString());
        }

        System.out.println("開始排序");
        long startTime = System.nanoTime();//納秒 比毫秒的精度高
        list.parallelStream().sorted().count();

        long endTime = System.nanoTime(); //納秒, 結束時間

        long millis = TimeUnit.NANOSECONDS.toMillis(endTime - startTime);
        System.out.println("耗時:" + millis + "毫秒");
    }
}

運行結果:並行耗時:1.1秒

image-20200104185700662

並行和串行 - 時間成本相差:3-5倍.

短路

public class StreamTest10 {
    public static void main(String[] args) {
        List<String> list = Arrays.asList("hello", "world", "hello world");
        //找出列表中,長度爲5的第一個單詞,同時將長度5打印出來.
//        list.stream().mapToInt( String::length).filter(length -> length == 5).findFirst().ifPresent(System.out::println);

        list.stream().mapToInt(item -> {
            int length = item.length();
            System.out.println(item);
            return length;
        }).filter(length -> length == 5).findFirst().ifPresent(System.out::println);

    }
}

結果集:

image-20200104190933025

爲何打印的時候只打印了1個?

緣由:容器裏面存放的是對每個容器的操做.

當對流進行迭代,處理的時候,會拿着容器的操做,會逐個的運用到值上.這

若是不知足過濾規則,則還會發生短路運算操做.這是緣由之二.只要找到符合條件的,後面就都不會運行.

如:沒有知足的規則,則會進行所有執行完.因此就會出現以下結果:

image-20200104191315339

案例:找出集合中全部的單詞,並去重.(flatMap方法的應用)

public class StreamTest11 {
    public static void main(String[] args) {

        //找出集合中全部的單詞,並去重.
        List<String> list = Arrays.asList("hello world", "hello welcome", "hello", "hello world hello", "hello world welcome");
        //要輸出: hello world welcome.
//        list.stream().map(item -> item.split(" ")).distinct().collect(Collectors.toList()); //不對
        List<String> collect = list.stream().map(item -> item.split(" ")).flatMap(Arrays::stream).distinct().collect(Collectors.toList());
        collect.forEach(System.out::println);
    }
}

案例:將兩個集合組合起來, 打招呼-人名(flatMap的應用)

public class StreamTest12 {
    public static void main(String[] args) {
        //將兩個集合組合起來,  打招呼-人名
        List<String> list1 = Arrays.asList("Hi", "Hello", "你好");
        List<String> list2 = Arrays.asList("zhangsan", "lisi", "wangwu", "zhaoliu");

//        list1.stream().map(item->item.concat(list2.stream().map()))
        List<String> collect = list1.stream().flatMap(item -> list2.stream().map(item2 ->item+ " " + item2)).collect(Collectors.toList());
        collect.forEach(System.out::println);
    }
}

分組和分區

如SQL中的group by.

select * from studeny group by name;

Result:Map<String,List >

傳統的實現思路:

  1. 循環列表
  2. 取出學生的名字
  3. 檢查map中是否存在該名字,不存在則直接添加到該map;存在則將map中的List對象取出來,而後將該Student對象添加到List中.
  4. 返回Map對象

經過流的方式來實現分組(groupingby()方法)

public class StreamTest13 {
    public static void main(String[] args) {
        Student student1 = new Student("zhangsan", 100, 20);
        Student student2 = new Student("lisi", 90, 20);
        Student student3 = new Student("wangwu", 90, 30);
        Student student4 = new Student("zhaoliu", 80, 40);

        List<Student> students = Arrays.asList(student1, student2, student3, student4);

        //Map<String, List<Student>> collect = students.stream().collect(Collectors.groupingBy(Student::getName));
       // System.out.println(collect);
        Map<Integer, List<Student>> collect = students.stream().collect(Collectors.groupingBy(Student::getScore));
        System.out.println(collect);
    }
}

這種SQL如何用流來實現?

select name,count(*) from student group by name;

很容易:

Map<String, Long> collect = students.stream().collect(Collectors.groupingBy(Student::getName, Collectors.counting()));
        System.out.println(collect);

先實現名字的分組,而後再取組內的平均值如何用流實現?

Map<String, Double> collect = students.stream().collect(Collectors.groupingBy(Student::getName, Collectors.averagingDouble(Student::getScore)));
        System.out.println(collect);

以上所寫的都是關於分組的概念.

分組:group by

分區:partition by

分區

分區是分組的特例,好比Boolean,只有true和false. 上述案例,好比以90分爲分界點,分區

Map<Boolean, List<Student>> collect = students.stream().collect(Collectors.partitioningBy(student -> student.getScore() > 90));
        System.out.println(collect);
collect.get(true);//獲取ture對應的值
collect.get(false);//獲取false對應的值
相關文章
相關標籤/搜索