Java SE 8 Lambda 标准库概览

8,Collectors

在目前的例子中,我们已经使用了collect()方法来收集流中的元素并放入List或Set中了。collec()方法的参数是一个收集器,它包含了收集或汇总多个元素到一个数据结构中的概念。Collectors 类提供了诸多常用收集器的工厂方法;toList()和toSet()是最常用的两种,但是还有很多可以用来执行复杂转换的收集器。

收集器的输入和输出均为参数化的。toList() 的 收集器接受一个T类型的输入并产生一个List类型的输出。一个稍微复杂一点的收集器是toMap,其中有多个版本。最简单的版本接受一对函数,一个用来映射键,另一个用来映射值。它接受一个T作为输入并产生一个Map类型的输出,其中K和V键值映射函数的结果类型。(更加复杂的版本允许我们自定义输出结果的类型,或者解决多个元素映射同一个键时的重复)举个例子,创建一个已知唯一键的反向索引,如catalog number:

Map albumsByCatalogNumber =

albums.stream()

.collect(Collectors.toMap(a -> a.getCatalogNumber(), a -> a));

与toMap相关的是groupingBy。假如我们想要将最喜欢的歌曲以歌手分组制成表格。我们希望有一个接受歌曲(Track)为输入,Map>为输出的收集器。这切好与groupingBy的简单形式的行为像匹配,它接受一个分类函数,并根据该函数生成一个映射键,该键的对应值是一组与键相关的输入元素。

Map> favsByArtist =

tracks.stream()

.filter(t -> t.rating >= 4)

.collect(Collectors.groupingBy(t -> t.artist));

收集器可以被组合重用来产生更加复杂的收集器。groupingBy的简单形式根据分类函数将元素组织到桶位中(这里是歌手),然后将所有映射到同一个桶位的元素放入List中。还有一个更加通用的版本允许我们使用另一个收集器来组织同一个桶位中的元素;这个版本接受一个分类函数,以及一个下游收集器作为参数,并且根据分类函数映射到同一个桶位的所有元素被收集到下游收集器中。(一个参数版本的groupingBy隐式的使用toLit()作为下游收集器)举个例子,如果我们想要收集不同歌手的歌曲到一个Set中而不是List,我们可以使用toSet()收集器:

Map> favsByArtist =

tracks.stream()

.filter(t -> t.rating >= 4)

.collect(Collectors.groupingBy(t -> t.artist,

Collectors.toSet()));

如果我们想要将歌曲以歌手和评价等级分类,来创建一个多层映射,我们可以这样:

Map>> byArtistAndRating =

tracks.stream()

.collect(groupingBy(t -> t.artist,

groupingBy(t -> t.rating)));

最后一个例子,假如我们想要创建一个歌曲标题中单次的频率分布。我们首先使用Stream.flatMap() 和Pattern.splitAsStream()来将歌曲的名称分割成不同的单词,并产生一个所有歌曲名字中单词的流。然后我们可以使用groupingBy作为分类函数,该函数以String.toUpperCase分类(这样所有相同的单词,忽略大小写,都被认为是相同的,并放入同一桶位中),再使用count()收集器作为下游收集器来计算每个单词出现的次数(没有直接创建一个集合):

Pattern pattern = Pattern.compile(\\s+");

Map wordFreq =

tracks.stream()

.flatMap(t -> pattern.splitAsStream(t.name)) // Stream

.collect(groupingBy(s -> s.toUpperCase(),

counting()));

flatMap()方法接受一个函数作为参数,该函数映射输入元素(这里是歌曲)到某类流中(这里是歌曲名字的单词)。它将映射函数应用到流中的每一个元素,然后用结果流中的内容替换每一个元素。(将这一过程分为两个操作,首先将每一个元素映射到一个流中,然后将结果流中的所有内容整合到一个流中)那么这里,flatMap操作的结果是一个包含了所有歌曲的名称单词的流。然后我们将单词分组到包含出现的单词的桶位中,最后使用counting()收集器来计算每个桶位中单词的个数。

Collectors类有许多构造不同收集器的方法,这些收集器可以用于所有常见的查询,统计,制表,并且你也可以实现自己的收集器。

9,并行的底层原理

随着Java SE 7中加入了Fork/Join框架,JDK中便有了高效实现的并行计算API。然而,使用Fork/Join的并行代码与同等功能的串行代码看起来非常不同(并且更大),这一直是并行化的一个障碍。通过在并行流和串行流上支持完全相同的操作,用户可以移除这一障碍,不需要重写代码便可在串行和并行之间自由切换,这使得并行更加容易并且不易出错。

通过递归分解实现一个并行计算所涉及的步骤包括:将一个问题分为一些子问题,解决子问题并产生部分结果,然后将这些部分结果合并。Fork/Join机制被设计为自动化这一过程。

为了支持在任意流数据源上的多有操作,我们将流数据源抽象为一个叫做Spliterator的模型,这是传统Iterator的一个广义化。为了支持顺序访问数据元素,Spliterator还支持分解:就像一个Iterator允许你切除单个元素并保持剩余元素依旧为Iterator,Spliterator允许你在输入元素上分割一个更大的数据块生成一个新的Spliterator,并切保持剩余的数据依旧是最初的Spliterator。(两个Spliterator依旧可以再分割)另外,Spliterator可以提供数据源的元数据像是元素个数(如果知道)以及一些列boolean值(像是‘元素是有序的’),这些用来优化Stream框架。

这一方法将递归分解的结构性质与在可分解的数据结构上进行并行计算的算法分离开来。数据结构的作者只需提供一个分界逻辑,然后就可以直接在流操作的并行计算上获益了。

大部分用户不需要实现Spliterator;他们只需要在集合上使用stream()方法即可。但是如果你曾经实现一个集合或其他流数据源,那么可能你想自定义一个Spliterator。Spliterator的API如下:

public interface Spliterator {

// Element access

boolean tryAdvance(Consumer super T> action);

void forEachRemaining(Consumer super T> action);

// Decomposition

Spliterator trySplit();

// Optional metadata

long estimateSize();

int characteristics();

Comparator super T> getComparator();

}

基本接口像Iterable和Collection提供了正确但低效的spliterator实现,但是子接口(如Set)和一些具体实现(如ArrayList)利用父接口不可用的信息实现了更加高效的spliterator。spliterator实现的质量将会影响流的执行性能;从split方法分割出均衡的结果会提高CPU的利用率,并且提供正确的元数据还会有其他优化。

10,出现顺序

许多数据源如列表,数组,以及I/O通道,都有一个自然的出现顺序,这意味着元素出现的顺序是有意义的。其他的数据源,像是HashSet则没有定义出现顺序(因此HashSet的Iterator允许以任意顺序提供元素)

Spliterator中维护的一个boolean属性,并用于流实现的就是当前流是否是定义出现顺序的。除了一些例外(如Stream.forEach()和Stream.findAny()),并行操作是被约束为需要出现顺序的。这意味着像下面的流管道:

List names = people.parallelStream()

.map(Person::getName)

.collect(toList());

name必须与数据源中相应people保持相同顺序。通常这是我们想要的,并且对于许多流操作,这不需要额外的消耗。另一方面,如果数据源是HashSet,name可能会出现任意顺序,甚至并行操作下出现不同顺序。

11,JDK中的流和Lambda

将Stream暴露为一个顶级抽象模型,我们想要确保流的特性能尽可能的贯穿整个JDK,Collection已经被增强为通过stream()和parrallelStream()可以转换为流;数组可以通过Arrays.stream()转换为流。

另外Stream中有一些静态工厂方法(以及相关的基本类型具体实现)用来创建流,像是Stream.of,Stream.generate,和IntStream.range。许多其他类也有了一些流转换方法,像是String.chars,BufferedReader.lines,Pattern.splitAsStream,Random.ints和BitSet.stream。

最后,我们提供一些列被库作者使用的API来构造流,他们希望暴露的非标准的流聚合功能。用来创建流的最小的信息是一个Iterator,但是如果创建者有额外的元数据(如大小),库可以通过实现Spliterator来提供更加高效的流实现。

12,比较器工厂

Comparator 类中添加了许多新的用来构建比较器的方法。

静态方法Comparator.comparing()接受一个提取Comparable排序键的函数并生成一个Comparator。它的实现非常简单:

public static > Comparator comparing(

Function super T, ? extends U> keyExtractor) {

return (c1, c2)

-> keyExtractor.apply(c1).compareTo(keyExtractor.apply(c2));

}

这样的方法是高阶函数的一个例子---接受一个函数作为参数或者生成一个新的函数的函数。但是这种方法真正强大之处是它的更好的可组合性。举个例子,Comparator有一个默认的方法来翻转比较方向。那么,为了将people别表以lastname 反序排序,我们只需简单的创建一个之前的比较器,然后要求它翻转自己即可:

people.sort(comparing(p -> p.getLastName()).reversed());

同样的,默认方法thenComparing允许我们接受一个Comparator并且改善它的行为当最初的比较器视两个元素相同时。以lastname和firtname排序people列表,我们可以这样:

Comparator c = Comparator.comparing(p -> p.getLastName())

.thenComparing(p -> p.getFirstName());

people.sort(c);

13,可变集合操作

集合上的流操作生成一个新的值,集合,或者副作用。然而,有时我们就想改变原始集合,并且Collection,List,Map中已经添加了一些新方法来利用Lambda表达式,像是Iterable.forEach(Consumer),Collection.removeAll(Predicate),List.replaceAll(UnaryOperator),List.sort(Comparator),以及Map.computeIfAbsent()。另外,ConcurrentMap中的非原子版本的方法,如replace和putIfAbsent已经从Map中去除了。

14,总结

向语言中添加Lambda表达式是一个巨大的进步,开发者每天都在使用核心库来完整他们的工作,因此语言的烟花需要配合核心库的演化以便用户可以尽快使用新特性。新标准库的中心特性就是Stream的概念,这为我们提供了强大的基于数据集的聚合操作工具,并且已经深度整合到当前的集合类以及其他JDK类中来了。

java se翻译_(翻译)Java SE 8 Lambda 标准库概览(下)相关推荐

  1. java se安装_安装Java SE平台

    安装Java SE平台 Java SE平台是学习掌握Java语言的最佳平台,而掌握Java SE又是进一步学习Java EE和Java ME所必须的. 1.下载JDK1.7. 本书将使用针对Windo ...

  2. java se7 变化_[转] Java se 7新特性研究(二)

    今天主要研究Java se 7中异常处理的新功能.从今天开始正在将jdk7的说法改为java se 7跟oracle官网的一致 一.新增了try-with-resource 异常声明 在JDK7中只要 ...

  3. java web源代码_检测Java Web应用程序而无需修改其源代码

    java web源代码 与其他系统进行交互时,大多数Java Web应用程序都使用标准Java接口. 诸如Web页面或REST服务器之类的基于HTTP的服务是使用接口javax.servlet.Ser ...

  4. java核心教程_核心Java教程

    java核心教程 Welcome to Core Java Tutorial. I have written a lot on Core Java and Java EE frameworks. Th ...

  5. java array缓存_有java数组

    [JAVA零基础入门系列]Day10 Java中的数组 [JAVA零基础入门系列](已完结)导航目录 Day1 开发环境搭建 Day2 Java集成开发环境IDEA Day3 Java基本数据类型 D ...

  6. java 分割一个_分割java

    [java]分割字符串工具类,霸气 jdk自带的 java 分割字符串,分割string,可以根据多个条件去分割.比如逗号,分号,逗号或者分号. 比如一个字符串:"abc,def;gh,ij ...

  7. java 字符串包_包java字符串

    Java核心技术卷I基础知识3.6.3 不可变字符串 3.6.3 不可变字符串 String类没有提供用于修改字符串的方法.如果希望将greeting的内容修改为"Help!",不 ...

  8. bigint对应java什么类型_「JAVA」从格式化输出到扫描输入,深究Java正则表达式匹配之道

    字符串是不可变的 字符串是不可变的,也就是说当字符串的内容发生改变的时候,会创建一个新的String对象:但是如果内容没有发生改变的时候,String类的方法会返回原字符串对象的引用. 而正则表达式往 ...

  9. java 进程运行时间_将Java类作为子进程运行

    java 进程运行时间 我本周需要将Java类(而不是jar)作为子进程运行. 更确切地说,我想从测试内部产生一个新进程,而不是直接在测试内部(进程内)运行它. 我不认为这是幻想或复杂的事情. 但是, ...

最新文章

  1. java运行时异常中文_JAVA——运行时异常(RuntimeException)
  2. JS中,如何判断一个被转换的数是否是NaN
  3. Git学习总结(1)——简介与基本操作
  4. 下载MySQL安装包
  5. pdf文件转图片的两种方法
  6. 双绞线接法详解双绞线的标准的由来与分析
  7. P8842 [传智杯 #4 初赛] 小卡与质数2 题解
  8. .net微信 验证 Token完整代码
  9. Eclipse SVN:E200030:There are unfinished transactions detected
  10. 22考研|英语词汇该如何记忆?
  11. myeclipse building workspace如何禁止及提高myeclipse速度
  12. win10系统中如何不用360安全卫士等软件仍能实现清理垃圾,释放内存和安全防护的基本功能
  13. Windows、Mac配置hosts
  14. eclipse快捷键、设置及常用插件
  15. 单点登录:统一登录与退出
  16. 周六日闲暇之余,做一些轻松浪漫的事情 二
  17. C++实现声音文件的播放(OpenAL、Fmod、BASS、SFML)
  18. 计算机学院创新基金项目题目,大学生科技创新基金项目立项
  19. uloop分析和非阻塞标准输入的一种应用
  20. 三元一次方程组例题_三元一次方程组练习题及答案(带过程)

热门文章

  1. ASP.NET MVC 异常Exception拦截
  2. C#正则表达式引发的CPU跑高问题以及解决方法
  3. maven不能加载ojdbc14.jar的解决方法
  4. AI佳作解读系列(一)——深度学习模型训练痛点及解决方法
  5. 【解决方案】分布式定时任务解决方案
  6. kail linux安装软件提示“无法定位软件包”解决方法
  7. 人工神经网络相对于支持向量机有什么优势? [关闭]
  8. Math.Floor()和Math.Truncate()之间的区别
  9. 如何从文件的完整路径获取目录?
  10. 宇宙射线:它们对程序产生影响的概率是多少?