Java 8 Lambda(类库篇——Streams API,Collector和并行)

参考资料:

1、背景

自从lambda表达式成为Java语言的一部分之后,Java集合(Collections)API就面临着大幅变化。为了不推到重来,所以对现有的API进行改进。

  • 为现有的接口(例如CollectionListStream)增加扩展方法;

  • 在类库中增加新的(stream,即java.util.stream.Stream)抽象以便进行聚集(aggregation)操作;

  • 改造现有的类型使之可以提供流视图(stream view);

  • 改造现有的类型使之可以容易的使用新的编程模式,这样用户就不必抛弃使用以久的类库(并不是说集合API会常驻永存)。

  • 提供更加易用的并行(Parallelism)库。供显式(explicit)但非侵入(unobstrusive)的并行。

2、内部迭代和外部迭代

  • 目前的集合库主要依赖于外部迭代:应用程序负责了做什么和怎么做

  • 外部迭代的循环是串行的,且集合框架无法对控制流程进行优化(排序、短路求值等)。

    外部迭代.png

  • 内部迭代,将流程控制权交给类库:应用程序只负责做什么,怎么做交由类库

    内部迭代.png

3、流(Stream)

  • 每个流代表一个值序列,流提供一系列常用的聚集操作,使得我们可以便捷的在它上面进行各种运算。

    java 7 实现:发现 type 为 grocery 的所有交易,然后返回以交易值降序排序好的交易 ID 集合

    List<Transaction> groceryTransactions = new Arraylist<>();

    for(Transaction t: transactions){

    if(t.getType() == Transaction.GROCERY){

    groceryTransactions.add(t);

    }

    }

    Collections.sort(groceryTransactions, new Comparator(){

    public int compare(Transaction t1, Transaction t2){

    return t2.getValue().compareTo(t1.getValue());

    }

    });

    List<Integer> transactionIds = new ArrayList<>();

    for(Transaction t: groceryTransactions){

    transactionsIds.add(t.getId());

    }

    java 8 的实现:发现 type 为 grocery 的所有交易,然后返回以交易值降序排序好的交易 ID 集合

    List<Integer> transactionsIds = transactions.parallelStream().

    filter(t -> t.getType() == Transaction.GROCERY).

    sorted(comparing(Transaction::getValue).reversed()).

    map(Transaction::getId).

    collect(toList());

  • 数据源任何可以用Iterator描述的对象都可以成为流的数据源,包括Collection类型、BufferedReader、Random、BitSet以及数组(Arrays.stream());流中元素的遍历顺序与数据源的遍历顺序一致。

  • 流的构成获取一个数据源(source)→ 数据转换(返回新的流)→执行操作获取想要的结果。

    流管道的构成.png

  • 流的操作主要分为两类:

    1. 中间(Intermediate):一个流可以后面跟随零个或多个 intermediate 操作,每个操作返回一个新的流;这类操作都是惰性化的(lazy),并没有真正开始流的遍历。

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

    2. 终止(Terminal):一个流只能有一个 terminal 操作。Terminal 操作的执行,才会真正开始流的遍历,并且会生成一个结果。

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

    3. 还有一类比较特别的操作Short-circuiting:用于处理操作一个无限大的流。

      anyMatch、 allMatch、 noneMatch、 findFirst、 findAny、 limit

4、流和集合

  • 集合主要用来对其元素进行有效(effective)的管理和访问(access),而流并不支持对其元素进行直接操作或直接访问,只是通过声明式操作在其上进行运算然后得到结果。

  • 无存储:流并不存储值;流的元素源自数据源(可能是某个数据结构、生成函数或I/O通道等等),通过一系列计算步骤得到;

  • 天然的函数式风格(Functional in nature):对流的操作会产生一个结果,但流的数据源不会被修改;

  • 惰性求值:多数流操作(包括过滤、映射、排序以及去重)都可以以惰性方式实现。这使得我们可以用一遍遍历完成整个流水线操作,并可以用短路操作提供更高效的实现;

  • 无需上界(Bounds optional):不少问题都可以被表达为无限流(infinite stream):用户不停地读取流直到满意的结果出现为止(比如说,枚举完美数这个操作可以被表达为在所有整数上进行过滤)。集合是有限的,但流不是(操作无限流时我们必需使用短路操作,以确保操作可以在有限时间内完成);

5、惰性

  • 假设我们现在要统计一个List<Person>里面的男性个数

    1. public int countMan(){  

    2. return personList.stream().filter(person -> person.getGender().equal(“男”)).count();  

    3. }  

  • Stream流处理的工作:获取所有人;过滤“男性”;计数。

    • 我们最后调用count()就是获取结果,相当于获取新的List的size。实际上,Stream并没有new一个新List。如果我们不调用count,那么Stream什么都不做。下面这段代码不会打印任何东西:

      1. public void function(){  

      2.     personList.stream().filter(person -> {  

      3.         System.out.println(“Go!”);  

      4.         person.getGender().equal(“男”);  

      5.     });  

6、并行

  • java 8的Stream流的并行处理是以java 7推出的fork/join框架来处理的。

  • 流水线既可以串行执行也可以并行执行,并行或串行是流的属性。

  • 虽然需要显示的指定并行流,但是是非侵入式的(侵入式一般需要实现某个接口,重写某个方法等),不需要开发人员手动实现并行代码。

    并行

    串行

    int sum = shapes.parallelStream()

    .filter(s -> s.getColor = BLUE)

    .mapToInt(s -> s.getWeight())

    .sum();

    int sum = shapes.stream()

    .filter(s -> s.getColor = BLUE)

    .mapToInt(s -> s.getWeight())

    .sum();

  • 因为并行的存在,所以stream流的操作应保证无干扰性:

    1. 不要干扰数据源(这个条件和遍历集合时所需的条件相似,如果集合在遍历时被修改,绝大多数的集合实现都会抛出ConcurrentModificationException)。

    2. 不要干扰其它lambda表达式,当一个lambda在修改某个可变状态而另一个lambda在读取该状态时就会产生这种干扰(所以在lambda表达式只允许使用有效只读的变量,对应用开放,对值封闭)。

7、应用示例

  • 遍历所有声明的方法,然后根据方法名称、返回类型以及参数的数量和类型进行匹配。

不使用Stream流

for (Method method : enclosingInfo.getEnclosingClass().getDeclaredMethods()) {

if (method.getName().equals(enclosingInfo.getName())) {

Class< ? >[] candidateParamClasses = method.getParameterTypes();

if (candidateParamClasses.length == parameterClasses.length) {

boolean matches = true;

for (int i = 0; i < candidateParamClasses.length; i += 1) {

if (!candidateParamClasses[i].equals(parameterClasses[i])) {

matches = false;

break;

}

}

if (matches) { // finally, check return type

if (method.getReturnType().equals(returnType)) {

return method;

}

}

}

}

}

throw new InternalError("Enclosing method not found");

使用Stream流

return Arrays.stream(enclosingInfo.getEnclosingClass().getDeclaredMethods())

.filter(m -> Objects.equal(m.getName(), enclosingInfo.getName()))

.filter(m -> Arrays.equal(m.getParameterTypes(), parameterClasses))

.filter(m -> Objects.equals(m.getReturnType(), returnType))

.findFirst()

.orElseThrow(() -> new InternalError("Enclosing method not found"));

  • 比较可以发现,Stream流不仅消除了所有的临时变量,而且代码更加紧凑,可读性更好,也不容易出错。

  • 假设我们需要得到一个按名字排序的专辑列表,专辑列表里面的每张专辑都至少包含一首四星及四星以上的音轨,为了构建这个专辑列表,我们可以这么写:

不使用Stream流:

List<Album> favs = new ArrayList<>(); 
        for (Album album : albums) { 
            boolean hasFavorite = false; 
            for (Track track : album.tracks) { 
                if (track.rating >= 4) { 
                    hasFavorite = true; 
                    break; 
                } 
            } 
            if (hasFavorite) 
                favs.add(album); 
        } 
        Collections.sort(favs, new Comparator<Album>() { 
            public int compare(Album a1, Album a2) { 
                return a1.name.compareTo(a2.name); 
            } 
        });

使用Stream流:

List<Album> sortedFavs = 
    albums.stream() 
          .filter(a -> a.tracks.anyMatch(t -> (t.rating >= 4))) 
          .sorted(Comparator.comparing(a -> a.name)) 
          .collect(Collectors.toList());

8、收集器(Collectors)

  • 流通过.collect()方法返回一个集合,该方法接收一个类型为Collector的参数。

  • Collectors提供了大量常用的工厂方法,根据不同的入参返回不同的实现,有 toList()、toSet()、toMap(K,V)等,其中toMap方法还可以指定容器和解决key冲突方式。

  • groupingBy(Classify),还可以指定Map容器和收集器类型

    Map<Artist, List<Track>> favsByArtist = 
        tracks.stream() 
              .filter(t -> t.rating >= 4) 
              .collect(Collectors.groupingBy(t -> t.artist));
    Map<Artist, Set<Track>> favsByArtist = 
        tracks.stream() 
              .filter(t -> t.rating >= 4) 
              .collect(Collectors.groupingBy(t -> t.artist, 
                                             Collectors.toSet()));

9、并行的实质

  • 以java 7引入的Fork/Join模型为基础实现Stream流的并行计算

  • Fork/Join的实现原理如下,为了能够并行化任意流上的所有操作,我们把流抽象为Spliterator(支持顺序依次访问数据,也支持分解数据

    • 把问题分解为子问题;

    • 串行解决子问题从而得到部分结果(partial result);

    • 合并部分结果合为最终结果。

  • Spliterator与Iterator的用法区别在于,如果开发者还知道源的其他元数据时(数据大小),类库就可以通过Spliterator提供一个更加高效的实现(就像JDK中所有的集合一样)。

    Stream.iterate(0,n->n+3); 
    ArrayList arrayList = new ArrayList(); 
    Spliterator spliterator = arrayList.spliterator(); 
    Iterator iterator = arrayList.iterator(); 
    boolean parallel = true; 
    StreamSupport.stream(spliterator,parallel); 
    StreamSupport.stream(Spliterators.spliterator(iterator,arrayList.size(),Spliterator.DISTINCT),parallel);

10、比较器工厂(Comparator)

  • Comparator接口中新增了若干用于生成比较器的实用方法

  • 静态方法Comparator.comparing()接收一个函数,返回一个Comparator。这种方式不仅简洁了代码(无需匿名类),而且便于进行各种组合操作。

    //比较器反序
    people.sort(comparing(p -> p.getLastName()).reversed());
    //多条件排序
    Comparator<Person> c = Comparator.comparing(p -> p.getLastName()) 
                                     .thenComparing(p -> p.getFirstName()); 
    people.sort(c);

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值