当前位置: 首页 > 科技观察

Java8新特性探秘(三):揭开lambda最强作用之谜

时间:2023-03-13 03:17:03 科技观察

我们盼望lambda能给java带来闭包概念很久了,但是如果不使用它在收藏中,我们会失去很多价值。将现有接口迁移到lambda样式的问题已通过默认方法解决。在本文中,我们将深入剖析Java集合中的批量数据操作(bulkoperation),揭开lambda函数的神秘面纱。一、关于JSR335JSR是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");}上面的例子是我们之前的做法,也叫外层迭代,loop是固定的顺序循环。在现在的多核时代,如果我们要并行循环,就得修改上面的代码。能提高多少效率还是决定性的,会带来一定的风险(线程安全问题等)。为了描述内部迭代,我们需要使用类库,例如Lambda。下面使用lambda和Collection.forEach重写上面的循环persons.forEach(p->p.setLastName("Doe"));现在由jdk库完成控制循环结束。我们不需要关心姓氏是如何设置给每个人对象的。库可以根据运行环境决定如何做,并行,乱序还是延迟加载。这是内部迭代,客户端将行为p.setLastName作为数据传递给API。实际上,内部迭代与集合的批量操作关系并不密切。有了它,我们就能感受到语法表达的变化。与批处理操作相关的真正有趣的是新的流API。JDK8新增了java.util.stream包。3.StreamAPIStream(Stream)只代表数据流,没有数据结构,遍历一次后就不能再遍历了(this编程的时候需要注意,不像Collection,还是要遍历很多次)data),它的source可以是Collection,array,io等。3.1中端方法流的作用是提供一个接口用于运营大数据,让数据运营更简单、更快捷。它有过滤、映射、减少遍历次数等方法。这些方法分为两类:中间方法和终端方法。“stream”抽象本质上应该是连续的,中间方法总是返回Stream,所以如果我们想要得到最终的结果,就必须使用端点操作来收集stream产生的最终结果。这两个方法的区别就是看他的返回值。如果是Stream,就是中间方法,否则就是结束方法。具体可以参考Stream的API。简单介绍几个中间方法(filter、map)和终端方法(collect、sum)3.1.1Filter是我们能想到的第一个也是最自然的操作,用来实现数据流中的过滤功能。Stream接口公开了一个过滤器方法,该方法接受Predicate实现,这些实现表示使用定义过滤条件的lambda表达式的操作。Listpersons=...StreampersOver18=persons.stream().filter(p->p.getAge()>18);//过滤18岁以上的人3.1.2Map假设我们现在过滤一些数据,比如转换对象的时候.Map操作允许我们执行一个Function实现(Function的泛型T和R分别代表执行输入和执行结果),它接受输入参数并返回。首先,让我们看看如何将其描述为匿名内部类:Streamadult=persons.stream().filter(p->p.getAge()>18).map(newFunction(){@OverridepublicAdultapply(Personperson){returnnewAdult(person);//将18岁以上的人转换为成年人}});现在,将上面的示例转换为lambda表达式:Streammap=persons.stream().filter(p->p.getAge()>18).map(person->newAdult(person));3.1.3Countcount方法是一个流的端点方法,可以对流结果进行最终统计,返回一个int。比如我们计算18岁的总人数intcountOfAdult=persons.stream().filter(p->p.getAge()>18).map(person->newAdult(person)).count();3.1.4Collectcollect方法也是一个stream的结束方法,可以收集最终结果ListadultList=persons.stream().filter(p->p.getAge()>18).map(person->newAdult(person)).collect(收集器.toList());或者,如果我们想使用特定的实现类来收集结果:ListadultList=persons.stream().filter(p->p.getAge()>18).map(person->newAdult(person)).collect(Collectors.toCollection(ArrayList::new));篇幅有限,其他的中间方法和终端方法就不一一介绍了。看完上面的例子,大家就可以明白这两种方法的区别了,后面大家可以根据自己的需要来决定使用。.3.2顺序流和并行流每个Stream有两种模式:顺序执行和并行执行。顺序流:Listpeople=list.getStream.collect(Collectors.toList());并行流:Listpeople=list.getStream.parallel().collect(Collectors.toList());顾名思义,在使用顺序遍历的时候,每读完一项就去读下一项。使用并行遍历时,数组被分成多段,每一段在不同的线程中处理,结果一起输出。3.2.1并行流原理:ListoriginalList=someData;split1=originalList(0,mid);//将数据拆分成小块split2=originalList(mid,end);newRunnable(split1.process());//小部分执行操作newRunnable(split2.process());ListrevisedList=split1+split2;//合并结果。如果你对hadoop稍有了解,就会知道MapReduce本身就是一个用于并行处理大数据集的软件框架。它处理大数据M??apReduce的核心思想是把它做大做小,分配不同的机器来运行map,最后通过reduce将所有机器的结果结合起来,得到一个最终的结果。与MapReduce不同,Stream采用多核技术,通过多核并行处理大数据,而MapReduce可以分布式。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);3.3Folk/Join框架应用硬件的并行性在java7中有了,即java.util.concurrent包的新功能之一是fork-join风格的并行分解框架,它也非常强大和高效。有兴趣的同学可以研究一下。我不会在这里详细介绍。与Stream.parallel()相比,我更倾向于后者。4.综上所述,如果没有lambda,Stream用起来相当别扭,会生成大量的匿名内部类,比如上面3.1.2map的例子。如果没有默认方法,集合框架的改变必然会带来很多变化,所以lambda+默认方法让jdk库更加强大和灵活,Stream和集合框架的改进就是最好的证明。java8特性探索系列我已经写了3篇文章。作为一顿大餐,java8的重量级特性,lambda和defaultmethod,都写在了前面。探索,感谢大家的支持,欢迎提出建议。如果你想知道有哪些功能,欢迎给我留言。原文链接:https://img.ydisp.cn/news/20220914/q0etncoi3d5