Skip to main content

JDK8 Stream流的用法

Stream 不是集合元素,它不是数据结构并不保存数据,它是有关算法和计算的,它更像一个高级版本的 Iterator。原始版本的 Iterator,用户只能显式地一个一个遍历元素并对其执行某些操作;高级版本的 Stream,用户只要给出需要对其包含的元素执行什么操作,比如 “过滤掉长度大于 10 的字符串”、“获取每个字符串的首字母”等,Stream 会隐式地在内部进行遍历,做出相应的数据转换。

Stream 就如同一个迭代器(Iterator),单向,不可往复,数据只能遍历一次,遍历过一次后即用尽了,就好比流水从面前流过,一去不复返.

流的操作类型分为两种:

  • Intermediate:一个流可以后面跟随零个或多个 intermediate 操作。其目的主要是打开流,做出某种程度的数据映射/过滤,然后返回一个新的流,交给下一个操作使用。这类操作都是惰性化的(lazy),就是说,仅仅调用到这类方法,并没有真正开始流的遍历。
  • Terminal:一个流只能有一个 terminal 操作,当这个操作执行后,流就被使用“光”了,无法再被操作。所以这必定是流的最后一个操作。Terminal 操作的执行,才会真正开始流的遍历,并且会生成一个结果,或者一个 side effect。
  • 在对于一个 Stream 进行多次转换操作 (Intermediate 操作),每次都对 Stream 的每个元素进行转换,而且是执行多次,这样时间复杂度就是 N(转换次数)个 for 循环里把所有操作都做掉的总和吗?其实不是这样的,转换操作都是 lazy 的,多个转换操作只会在 Terminal 操作的时候融合起来,一次循环完成。我们可以这样简单的理解,Stream 里有个操作函数的集合,每次转换操作就是把转换函数放入这个集合中,在 Terminal 操作的时候循环 Stream 对应的集合,然后对每个元素执行所有的函数。

    当把一个数据结构包装成 Stream 后,就要开始对里面的元素进行各类操作了。常见的操作可以归类如下。

  • Intermediate:
  • map (mapToInt, flatMap 等)、 filter、 distinct、 sorted、 peek、 limit、 skip、 parallel、 sequential、 unordered

  • Terminal:
  • forEach、 forEachOrdered、 toArray、 reduce、 collect、 min、 max、 count、 anyMatch、 allMatch、 noneMatch、 findFirst、 findAny、 iterator

  • Short-circuiting:
  • anyMatch、 allMatch、 noneMatch、 findFirst、 findAny、 limit

    1. Filter
    根据谓语句表达式进行过滤
    1

    // filter
    list.stream().filter(e -> e.getAge() == 10).forEach(e -> System.out.println(e));

    2. Stream map(Function<? super T, ? extends R> mapper)
    根据给定的function做转换
    2

    // map
    Function<People, String> nameFun = (e) -> e.getName();
    list.stream().map(nameFun).forEach(e -> System.out.println(e));

    JDK自带了一些基本类型的map函数
    IntStream mapToInt(ToIntFunction<? super T> mapper);
    LongStream mapToLong(ToLongFunction<? super T> mapper);
    DoubleStream mapToDouble(ToDoubleFunction<? super T> mapper);

    3. Stream flatMap(Function<? super T, ? extends Stream<? extends R>> mapper)
    flatMap 把 input Stream 中的层级结构扁平化,就是将最底层元素抽出来放到一起
    3

    // flatmap
    Stream<List> inputStream = Stream.of(Arrays.asList(1), Arrays.asList(2, 3),
    Arrays.asList(4, 5, 6));
    Stream outputStream = inputStream.flatMap((childList) -> childList.stream());
    outputStream.forEach(e -> System.out.println(e));

    IntStream flatMapToInt(Function<? super T, ? extends IntStream> mapper);
    LongStream flatMapToLong(Function<? super T, ? extends LongStream> mapper);
    DoubleStream flatMapToDouble(Function<? super T, ? extends DoubleStream> mapper);

    4. Stream distinct()
    对于Stream中包含的元素进行去重操作(去重逻辑依赖元素的equals方法),新生成的Stream中没有重复的元素
    4

    5. Stream peek(Consumer<? super T> action)
    对流中的每个元素做特定的操作并返回原先的流
    // peak
    Stream.of(Arrays.asList(1), Arrays.asList(2, 3), Arrays.asList(4, 5, 6, 1, 2, 3))
    .flatMap((childList) -> childList.stream()).peek(e -> System.out.println(“—” + e)).count();

    6.void forEach(Consumer<? super T> action)
    对流中的每个元素做操作。这是一个流的结束操作。
    // peak
    Stream.of(Arrays.asList(1), Arrays.asList(2, 3), Arrays.asList(4, 5, 6, 1, 2, 3))
    .flatMap((childList) -> childList.stream()).peek(e -> System.out.println(“—” + e)).count();

    7. Object[] toArray()
    返回一个由stream包含所有元素的数组。
    Stream s = Stream.of(1,2,3);
    Object[] oarr = s.toArray();
    for(Object obj:oarr){
    System.out.println(obj);
    }
    8. Reduce

    T reduce(T identity, BinaryOperator accumulator);
    Optional reduce(BinaryOperator accumulator);
    U reduce(U identity,
    BiFunction<U, ? super T, U> accumulator,
    BinaryOperator combiner);

    这个方法的主要作用是把 Stream 元素组合起来。它提供一个起始值(种子),然后依照运算规则(BinaryOperator),和前面 Stream 的第一个、第二个、第 n 个元素组合。从这个意义上说,字符串拼接、数值的 sum、min、max、average 都是特殊的 reduce。例如 Stream 的 sum 就相当于
    Integer sum = integers.reduce(0, (a, b) -> a+b); 或
    Integer sum = integers.reduce(0, Integer::sum);
    也有没有起始值的情况,这时会把 Stream 的前面两个元素组合起来,返回的是 Optional。

    //reduce
    String concat = Stream.of(“A”, “B”, “C”, “D”).reduce(“”, String::concat);
    System.out.println(concat);
    double minValue = Stream.of(-1.5, 1.0, -3.0, -2.0).reduce(Double.MAX_VALUE, Double::min);
    System.out.println(minValue);
    int sumValue = Stream.of(1, 2, 3, 4).reduce(0, Integer::sum);
    System.out.println(sumValue);
    sumValue = Stream.of(1, 2, 3, 4).reduce(Integer::sum).get();
    concat = Stream.of(“a”, “B”, “c”, “D”, “e”, “F”).filter(x -> x.compareTo(“Z”) > 0).reduce(“”, String::concat);

    Stream的reduce方法,其实就是聚合或者汇聚的意思,由于Stream本身就代表着一堆数据,那stream.reduce()方法顾名思义就是把一堆数据聚合成一个数据。

    9. Match
    Stream 有三个 match 方法,从语义上说:
    allMatch:Stream 中全部元素符合传入的 predicate,返回 true
    anyMatch:Stream 中只要有一个元素符合传入的 predicate,返回 true
    noneMatch:Stream 中没有一个元素符合传入的 predicate,返回 true
    //code
    List persons = new ArrayList();
    persons.add(new Person(1, “name” + 1, 10));
    persons.add(new Person(2, “name” + 2, 21));
    persons.add(new Person(3, “name” + 3, 34));
    persons.add(new Person(4, “name” + 4, 6));
    persons.add(new Person(5, “name” + 5, 55));
    boolean isAllAdult = persons.stream().
    allMatch(p -> p.getAge() > 18);
    System.out.println(“All are adult? ” + isAllAdult);
    boolean isThereAnyChild = persons.stream().
    anyMatch(p -> p.getAge() < 12);
    System.out.println(“Any child? ” + isThereAnyChild);

    总之,Stream 的特性可以归纳为:

  • 不是数据结构
  • 它没有内部存储,它只是用操作管道从 source(数据结构、数组、generator function、IO channel)抓取数据
  • 它也绝不修改自己所封装的底层数据结构的数据。例如 Stream 的 filter 操作会产生一个不包含被过滤元素的新 Stream,而不是从 source 删除那些元素.每次都是产生一个新的stream/li>
  • 所有 Stream 的操作必须以 lambda 表达式为参数
  • 不支持索引访问,你可以请求第一个元素,但无法请求第二个,第三个,或最后一个。
  • 很容易生成数组或者 List
  • 惰性化 很多 Stream 操作是向后延迟的,一直到它弄清楚了最后需要多少数据才会开始
  • Intermediate 操作永远是惰性化的。
  • 并行能力 当一个 Stream 是并行化的,就不需要再写多线程代码,所有对它的操作会自动并行进行的
  • 可以是无限的.集合有固定大小,Stream 则不必。limit(n) 和 findFirst() 这类的 short-circuiting 操作可以对无限的 Stream 进行运算并很快完成
  • 可以简化代码