Java8新特性lambda表达式有什么用(用法实例)
我们期待了很久lambda为java带来闭包的概念,但是如果我们不在集合中使用它的话,就损失了很大价值。现有接口迁移成为lambda风格的问题已经通过defaultmethods解决了,在这篇文章将深入解析Java集合里面的批量数据操作(bulkoperation),解开lambda最强作用的神秘面纱。
1.关于JSR335
JSR是JavaSpecificationRequests的缩写,意思是Java规范请求,Java8版本的主要改进是Lambda项目(JSR335),其目的是使Java更易于为多核处理器编写代码。JSR335=lambda表达式+接口改进(默认方法)+批量数据操作。加上前面两篇,我们已是完整的学习了JSR335的相关内容了。
2.外部VS内部迭代
以前Java集合是不能够表达内部迭代的,而只提供了一种外部迭代的方式,也就是for或者while循环。
Listpersons=asList(newPerson("Joe"),newPerson("Jim"),newPerson("John")); for(Personp: persons){ p.setLastName("Doe"); }
上面的例子是我们以前的做法,也就是所谓的外部迭代,循环是固定的顺序循环。在现在多核的时代,如果我们想并行循环,不得不修改以上代码。效率能有多大提升还说定,且会带来一定的风险(线程安全问题等等)。
要描述内部迭代,我们需要用到Lambda这样的类库,下面利用lambda和Collection.forEach重写上面的循环
persons.forEach(p->p.setLastName("Doe"));
现在是由jdk库来控制循环了,我们不需要关心lastname是怎么被设置到每一个person对象里面去的,库可以根据运行环境来决定怎么做,并行,乱序或者懒加载方式。这就是内部迭代,客户端将行为p.setLastName当做数据传入api里面。
内部迭代其实和集合的批量操作并没有密切的联系,借助它我们感受到语法表达上的变化。真正有意思的和批量操作相关的是新的流(stream)API。新的java.util.stream包已经添加进JDK8了。
3.StreamAPI
流(Stream)仅仅代表着数据流,并没有数据结构,所以他遍历完一次之后便再也无法遍历(这点在编程时候需要注意,不像Collection,遍历多少次里面都还有数据),它的来源可以是Collection、array、io等等。
3.1中间与终点方法
流作用是提供了一种操作大数据接口,让数据操作更容易和更快。它具有过滤、映射以及减少遍历数等方法,这些方法分两种:中间方法和终端方法,“流”抽象天生就该是持续的,中间方法永远返回的是Stream,因此如果我们要获取最终结果的话,必须使用终点操作才能收集流产生的最终结果。区分这两个方法是看他的返回值,如果是Stream则是中间方法,否则是终点方法。具体请参照Stream的api。
简单介绍下几个中间方法(filter、map)以及终点方法(collect、sum)
3.1.1Filter
在数据流中实现过滤功能是首先我们可以想到的最自然的操作了。Stream接口暴露了一个filter方法,它可以接受表示操作的Predicate实现来使用定义了过滤条件的lambda表达式。
Listpersons=… StreampersonsOver18=persons.stream().filter(p->p.getAge()>18);//过滤18岁以上的人
3.1.2Map
假使我们现在过滤了一些数据,比如转换对象的时候。Map操作允许我们执行一个Function的实现(Function<T,R>的泛型T,R分别表示执行输入和执行结果),它接受入参并返回。首先,让我们来看看怎样以匿名内部类的方式来描述它:
Streamadult=persons .stream() .filter(p->p.getAge()>18) .map(newFunction(){ @Override publicAdultapply(Personperson){ returnnewAdult(person);//将大于18岁的人转为成年人 } });
现在,把上述例子转换成使用lambda表达式的写法:
Streammap=persons.stream() .filter(p->p.getAge()>18) .map(person->newAdult(person));
3.1.3Count
count方法是一个流的终点方法,可使流的结果最终统计,返回int,比如我们计算一下满足18岁的总人数:
intcountOfAdult=persons.stream() .filter(p->p.getAge()>18) .map(person->newAdult(person)) .count();
3.1.4Collect
collect方法也是一个流的终点方法,可收集最终的结果
ListadultList=persons.stream() .filter(p->p.getAge()>18) .map(person->newAdult(person)) .collect(Collectors.toList());
或者,如果我们想使用特定的实现类来收集结果:
ListadultList=persons .stream() .filter(p->p.getAge()>18) .map(person->newAdult(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并行流原理:
ListoriginalList=someData; split1=originalList(0,mid);//将数据分小部分 split2=originalList(mid,end); newRunnable(split1.process());//小部分执行操作 newRunnable(split2.process()); ListrevisedList=split1+split2;//将结果合并
3.2.2顺序与并行性能测试对比
如果是多核机器,理论上并行流则会比顺序流快上一倍,下面是测试代码
longt0=System.nanoTime();
//初始化一个范围100万整数流,求能被2整除的数字,toArray()是终点方法
inta[]=IntStream.range(0,1_000_000).filter(p->p%2==0).toArray();
longt1=System.nanoTime();
//和上面功能一样,这里是用并行流来计算
intb[]=IntStream.range(0,1_000_000).parallel().filter(p->p%2==0).toArray();
longt2=System.nanoTime();
//我本机的结果是serial:0.06s,parallel0.02s,证明并行流确实比顺序流快
System.out.printf("serial:%.2fs,parallel%.2fs%n",(t1-t0)*1e-9,(t2-t1)*1e-9);