zoukankan      html  css  js  c++  java
  • 解开lambda最强作用的神秘面纱

    我们期待了很久lambda为java带来闭包的概念,但是如果我们不在集合中使用它的话,就损失了很大价值。现有接口迁移成为lambda风格的问题已经通过default methods解决了,在这篇文章将深入解析Java集合里面的批量数据操作(bulk operation),解开lambda最强作用的神秘面纱。

    1.关于JSR335

    JSR是Java Specification Requests的缩写,意思是Java 规范请求,Java 8 版本的主要改进是 Lambda 项目(JSR 335),其目的是使 Java 更易于为多核处理器编写代码。JSR 335=lambda表达式+接口改进(默认方法)+批量数据操作。加上前面两篇,我们已是完整的学习了JSR335的相关内容了。

    2.外部VS内部迭代

    以前Java集合是不能够表达内部迭代的,而只提供了一种外部迭代的方式,也就是for或者while循环。

    List persons = asList(new Person("Joe"), new Person("Jim"), new Person("John"));
    for (Person p :  persons) {
       p.setLastName("Doe");
    }

    上面的例子是我们以前的做法,也就是所谓的外部迭代,循环是固定的顺序循环。在现在多核的时代,如果我们想并行循环,不得不修改以上代码。效率能有多大提升还说定,且会带来一定的风险(线程安全问题等等)。 
    要描述内部迭代,我们需要用到Lambda这样的类库,下面利用lambda和Collection.forEach重写上面的循环 

    persons.forEach(p->p.setLastName("Doe"));

    现在是由jdk 库来控制循环了,我们不需要关心last name是怎么被设置到每一个person对象里面去的,库可以根据运行环境来决定怎么做,并行,乱序或者懒加载方式。这就是内部迭代,客户端将行为p.setLastName当做数据传入api里面。 

    内部迭代其实和集合的批量操作并没有密切的联系,借助它我们感受到语法表达上的变化。真正有意思的和批量操作相关的是新的流(stream)API。新的java.util.stream包已经添加进JDK 8了。

    3.Stream API

    流(Stream)仅仅代表着数据流,并没有数据结构,所以他遍历完一次之后便再也无法遍历(这点在编程时候需要注意,不像Collection,遍历多少次里面都还有数据),它的来源可以是Collection、array、io等等。

    3.1中间与终点方法

    流作用是提供了一种操作大数据接口,让数据操作更容易和更快。它具有过滤、映射以及减少遍历数等方法,这些方法分两种:中间方法终端方法,“流”抽象天生就该是持续的,中间方法永远返回的是Stream,因此如果我们要获取最终结果的话,必须使用终点操作才能收集流产生的最终结果。区分这两个方法是看他的返回值,如果是Stream则是中间方法,否则是终点方法。具体请参照Stream的api

    简单介绍下几个中间方法(filter、map)以及终点方法(collect、sum)

    3.1.1Filter

    在数据流中实现过滤功能是首先我们可以想到的最自然的操作了。Stream接口暴露了一个filter方法,它可以接受表示操作的Predicate(断言)实现来使用定义了过滤条件的lambda表达式。

    List persons = …
    Stream personsOver18 = persons.stream().filter(p -> p.getAge() > 18);//过滤18岁以上的人

    3.1.2Map

    假使我们现在过滤了一些数据,比如转换对象的时候。Map操作允许我们执行一个Function的实现(Function<T,R>的泛型T,R分别表示执行输入和执行结果),它接受入参并返回。首先,让我们来看看怎样以匿名内部类的方式来描述它

    Stream adult= persons
                  .stream()
                  .filter(p -> p.getAge() > 18)
                  .map(new Function() {
                      @Override
                      public Adult apply(Person person) {
                         return new Adult(person);//将大于18岁的人转为成年人
                      }
                  });

    现在,把上述例子转换成使用lambda表达式的写法:

    Stream map = persons.stream()
                        .filter(p -> p.getAge() > 18)
                        .map(person -> new Adult(person));

    3.1.3Count

    count方法是一个流的终点方法,可使流的结果最终统计,返回int,比如我们计算一下满足18岁的总人数

    int countOfAdult=persons.stream()
                           .filter(p -> p.getAge() > 18)
                           .map(person -> new Adult(person))
                           .count();

    3.1.4Collect

    collect方法也是一个流的终点方法,可收集最终的结果

    List adultList= persons.stream()
                           .filter(p -> p.getAge() > 18)
                           .map(person -> new Adult(person))
                           .collect(Collectors.toList());

    或者,如果我们想使用特定的实现类来收集结果:

    List adultList = persons
                     .stream()
                     .filter(p -> p.getAge() > 18)
                     .map(person -> new Adult(person))
                     .collect(Collectors.toCollection(ArrayList::new));

    篇幅有限,其他的中间方法和终点方法就不一一介绍了,看了上面几个例子,大家明白这两种方法的区别即可,后面可根据需求来决定使用。

    3.2顺序流与并行流

    每个Stream都有两种模式:顺序执行和并行执行。
    顺序流:

    List <Person> people = list.getStream.collect(Collectors.toList());

    并行流:

    List <Person> people = list.getStream.parallel().collect(Collectors.toList());

    顾名思义,当使用顺序方式去遍历时,每个item读完后再读下一个item。而使用并行去遍历时,数组会被分成多个段,其中每一个都在不同的线程中处理,然后将结果一起输出。

    3.2.1并行流原理:

    List originalList = someData;
    split1 = originalList(0, mid);//将数据分小部分
    split2 = originalList(mid,end);
    new Runnable(split1.process());//小部分执行操作
    new Runnable(split2.process());
    List revisedList = split1 + split2;//将结果合并

    大家对hadoop有稍微了解就知道,里面的 MapReduce  本身就是用于并行处理大数据集的软件框架,其 处理大数据的核心思想就是大而化小,分配到不同机器去运行map,最终通过reduce将所有机器的结果结合起来得到一个最终结果,与MapReduce不同,Stream则是利用多核技术可将大数据通过多核并行处理,而MapReduce则可以分布式的。

    3.2.2顺序与并行性能测试对比

    如果是多核机器,理论上并行流则会比顺序流快上一倍,下面是测试代码

    long t0 = System.nanoTime();
    
            //初始化一个范围100万整数流,求能被2整除的数字,toArray()是终点方法
    
            int a[]=IntStream.range(0, 1_000_000).filter(p -> p % 2==0).toArray();
    
            long t1 = System.nanoTime();
    
            //和上面功能一样,这里是用并行流来计算
    
            int b[]=IntStream.range(0, 1_000_000).parallel().filter(p -> p % 2==0).toArray();
    
            long t2 = System.nanoTime();
    
            //我本机的结果是serial: 0.06s, parallel 0.02s,证明并行流确实比顺序流快
    
            System.out.printf("serial: %.2fs, parallel %.2fs%n", (t1 - t0) * 1e-9, (t2 - t1) * 1e-9);

    3.3关于Folk/Join框架

    应用硬件的并行性在java 7就有了,那就是 java.util.concurrent 包的新增功能之一是一个 fork-join 风格的并行分解框架,同样也很强大高效,有兴趣的同学去研究,这里不详谈了,相比Stream.parallel()这种方式,我更倾向于后者。

    4.总结

    如果没有lambda,Stream用起来相当别扭,他会产生大量的匿名内部类,比如上面的3.1.2map例子,如果没有default method,集合框架更改势必会引起大量的改动,所以lambda+default method使得jdk库更加强大,以及灵活,Stream以及集合框架的改进便是最好的证明。

    原文出处点击这里

  • 相关阅读:
    PythonStudy——数据类型总结 Data type summary
    PythonStudy——可变与不可变 Variable and immutable
    PythonStudy——列表操作 List operatio
    PythonStudy——列表的常用操作 List of common operations
    PythonStudy——列表类型 List type
    PythonStudy——字符串扩展方法 String extension method
    PythonStudy——字符串重要方法 String important method
    AWT,Swing,RCP 开发
    JQuery插件机制
    最新知识网站
  • 原文地址:https://www.cnblogs.com/UncleWang001/p/9843473.html
Copyright © 2011-2022 走看看