咱们期待了好久lambda为java带来闭包的概念,可是若是咱们不在集合中使用它的话,就损失了很大价值。现有接口迁移成为lambda风格的问题已经经过default methods解决了,在这篇文章将深刻解析Java集合里面的批量数据操做(bulk operation),解开lambda最强做用的神秘面纱。 html
JSR是Java Specification Requests的缩写,意思是Java 规范请求,Java 8 版本的主要改进是 Lambda 项目(JSR 335),其目的是使 Java 更易于为多核处理器编写代码。JSR 335=lambda表达式+接口改进(默认方法)+批量数据操做。加上前面两篇,咱们已经是完整的学习了JSR335的相关内容了。 java
之前Java集合是不可以表达内部迭代的,而只提供了一种外部迭代的方式,也就是for或者while循环。
编程
List persons = asList(new Person("Joe"), new Person("Jim"), new Person("John")); for (Person p : persons) { p.setLastName("Doe"); }上面的例子是咱们之前的作法,也就是所谓的外部迭代,循环是固定的顺序循环。在如今多核的时代,若是咱们想并行循环,不得不修改以上代码。效率能有多大提高还说定,且会带来必定的风险(线程安全问题等等)。
persons.forEach(p->p.setLastName("Doe"));如今是由jdk 库来控制循环了,咱们不须要关心last name是怎么被设置到每个person对象里面去的,库能够根据运行环境来决定怎么作,并行,乱序或者懒加载方式。这就是内部迭代,客户端将行为p.setLastName当作数据传入api里面。
内部迭代其实和集合的批量操做并无密切的联系,借助它咱们感觉到语法表达上的变化。真正有意思的和批量操做相关的是新的流(stream)API。新的java.util.stream包已经添加进JDK 8了。 api
流(Stream)仅仅表明着数据流,并无数据结构,因此他遍历完一次以后便再也没法遍历(这点在编程时候须要注意,不像Collection,遍历多少次里面都还有数据),它的来源能够是Collection、array、io等等。 数组
流做用是提供了一种操做大数据接口,让数据操做更容易和更快。它具备过滤、映射以及减小遍历数等方法,这些方法分两种:中间方法和终端方法,“流”抽象天生就该是持续的,中间方法永远返回的是Stream,所以若是咱们要获取最终结果的话,必须使用终点操做才能收集流产生的最终结果。区分这两个方法是看他的返回值,若是是Stream则是中间方法,不然是终点方法。具体请参照Stream的api。
简单介绍下几个中间方法(filter、map)以及终点方法(collect、sum)
安全
在数据流中实现过滤功能是首先咱们能够想到的最天然的操做了。Stream接口暴露了一个filter方法,它能够接受表示操做的Predicate实现来使用定义了过滤条件的lambda表达式。
数据结构
List persons = … Stream personsOver18 = persons.stream().filter(p -> p.getAge() > 18);//过滤18岁以上的人
假使咱们如今过滤了一些数据,好比转换对象的时候。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));
count方法是一个流的终点方法,可以使流的结果最终统计,返回int,好比咱们计算一下知足18岁的总人数
app
int countOfAdult=persons.stream() .filter(p -> p.getAge() > 18) .map(person -> new Adult(person)) .count();
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));
篇幅有限,其余的中间方法和终点方法就不一一介绍了,看了上面几个例子,你们明白这两种方法的区别便可,后面可根据需求来决定使用。
每一个Stream都有两种模式:顺序执行和并行执行。
顺序流:
List <Person> people = list.getStream.collect(Collectors.toList());并行流:
List <Person> people = list.getStream.parallel().collect(Collectors.toList());顾名思义,当使用顺序方式去遍历时,每一个item读完后再读下一个item。而使用并行去遍历时,数组会被分红多个段,其中每个都在不一样的线程中处理,而后将结果一块儿输出。
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则能够分布式的。
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);
应用硬件的并行性在java 7就有了,那就是 java.util.concurrent 包的新增功能之一是一个 fork-join 风格的并行分解框架,一样也很强大高效,有兴趣的同窗去研究,这里不详谈了,相比Stream.parallel()这种方式,我更倾向于后者。
若是没有lambda,Stream用起来至关别扭,他会产生大量的匿名内部类,好比上面的3.1.2map例子,若是没有default method,集合框架更改势必会引发大量的改动,因此lambda+default method使得jdk库更增强大,以及灵活,Stream以及集合框架的改进即是最好的证实。
java 8特性探究系列写了3篇了,做为大餐,将java 8的重量级特性lambda与default method写在前面,下篇上个小菜,荤素搭配,也是语言相关的,JEP104 Java 类型的注解的探究,同时谢谢你们的支持,欢迎提出建议。若是你想了解哪些特性,欢迎给我发留言。
转载时候请注明出处。 http://my.oschina.net/benhaile