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 进行运算并很快完成
  • 可以简化代码
  • JDK8 lambda表达式

    JDK8 中包含了很多内置的函数式接口。有些是在以前版本的Java中大家耳熟能详的,例如Comparator接口,或者Runnable接口。对这些现成的接口进行实现,可以通过@FunctionalInterface 标注来启用Lambda功能支持

    Predicate
    Predicate 是一个布尔类型的函数,该函数只有一个输入参数。Predicate接口包含了多种默认方法,用于处理复杂的逻辑动词(and,or, negate).

    1

    Example
    [java]
    public class PeopleDao {

    public List findByAge(List list, int age) {
    return list.stream().filter(e -&gt; e.getAge() == age).collect(Collectors.toList());
    }

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

    for (int i = 0; i &lt; 100; i++) {
    People people = new People();
    people.setAge(i);
    people.setStress("Shanxi");
    people.setName("P " + i);
    list.add(people);
    }

    PeopleDao dao = new PeopleDao();
    List res = dao.findByAge(list, 10);
    res.stream().forEach(e -&gt; System.out.println(e.toString()));

    Predicate agePredicate = (p) -&gt; p.getAge() == 10;
    list.stream().filter(agePredicate).forEach(p -&gt; System.out.println(p.toString()));
    }

    }

    [/java]
    JDK自带了几个基本类型的Predicate,就像DoublePredicate,LongPredicate,IntPredicate。

    Function
    Function接口接收一个参数,并返回单一的结果。默认方法可以将多个函数串在一起(compse, andThen)
    2

    [java]
    package com.learn.core.lambda;

    import java.util.ArrayList;
    import java.util.List;
    import java.util.function.Function;
    import java.util.function.Predicate;
    import java.util.stream.Collectors;
    import com.learn.core.domain.People;

    /**
    * Created by caveup on 2017/2/20.
    */
    public class PeopleDao {

    public List<People> findByAge(List<People> list, int age) {
    return list.stream().filter(e -> e.getAge() == age).collect(Collectors.toList());
    }

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

    for (int i = 0; i < 100; i++) {
    People people = new People();
    people.setAge(i);
    people.setStress("Shanxi");
    people.setName("P" + i);
    list.add(people);
    }

    PeopleDao dao = new PeopleDao();
    List<People> res = dao.findByAge(list, 10);
    res.stream().forEach(e -> System.out.println(e.toString()));

    Predicate<People> agePredicate = (p) -> p.getAge() == 10;
    list.stream().filter(agePredicate).forEach(p -> System.out.println(p.toString()));

    Function<People, String> name = (p) -> p.getName();
    Function<String, String> upFunction = String::toUpperCase;

    System.out.println(name.apply(list.get(0)));
    System.out.println(upFunction.apply("hello"));

    Function<String, Integer> toInteger = Integer::valueOf;
    Function<String, String> backToString = toInteger.andThen(String::valueOf);
    System.out.println(backToString.apply("10"));
    }

    }
    [/java]

    Consumer
    Consumer代表了在一个输入参数上需要进行的操作,就是对输入的参数做一些行为或者不做任何行为。
    3

    [java]
    public class ConsumerLambda {

    public static void main(String[] args) {

    People people = new People();
    people.setAge(10);
    people.setStress("Shanxi");
    people.setName("Padfasdfa");

    Consumer<People> peopleConsumer = p -> System.out.println(p.toString());
    peopleConsumer.accept(people);
    }
    }

    [/java]

    Supplier
    Supplier接口产生一个给定类型的结果。与Function不同的是,Supplier没有输入参数
    4

    [java]
    public class Supplierlambda {

    public static void main(String[] args) {
    People people = new People();
    people.setAge(10);
    people.setStress("Shanxi");
    people.setName("Padfasdfa");

    Supplier<People> peopleSupplier = () -> new People();

    System.out.println(peopleSupplier.get().toString());
    }
    }

    [/java]

    日志输出中,必须使用条件输出形式或者使用占位符的方式

    比如以下例子:

    logger.debug(“Processing trade with id: ” + id + ” symbol: ” + symbol);

    如果日志是warn,上述日志不会打印,但是在执行字符串的操作时,如果symbol是对象,就会执行toString()方法,浪费了很多系统资源,但是日志又不需要打印。

    正确的方式:


    //condition
    if (logger.isDebugEnabled()) {
    logger.debug("Processing trade with id: " + id + " symbol: " + symbol);
    }

    //args
    logger.debug("Processing trade with id: {} symbol : {} ", id, symbol);

    阿里巴巴Java技术手册

    年初,阿里巴巴公布了他们的java技术手册,一直没有抽出时间拜读,今天不是很忙,就通读了一下,确实收获颇多。 自己做软件开发已经7年了,也积累了一些经验,文中有很多子项目是和自己平时开发一样的,但是从来没有这么详细的整理出来。保持一个良好的代码规范和风格对项目维护,项目更新以及新来员工的学习都是有更大帮助的。
    现在再次把这个手册公布出来,希望一直温故而知新。什么时候开始都不是最糟糕的结果,问题是什么时候开始。

    阿里巴巴Java开发手册

    —如果有版权问题,请详细本人。谢谢!