Java8函数式编程_5--高级集合类和收集器

1,免责声明,本文大部分内容摘自《Java8函数式编程》。在这本书的基础上,根据自己的理解和网上一些博文,精简或者修改。本次分享的内容,只用于技术分享,不作为任何商业用途。当然这本书是非常值得一读,强烈建议买一本!
2,本次分享的样例代码均上传到github上,请点击这里

注意:本章所有的例子大多围绕 1.3 节介绍的案例展开(音乐)。


第5章 高级集合类和收集器

第3章只介绍了集合类的部分变化,事实上,Java 8对集合类的改进不止这些。现在是时候介绍一些高级主题了,包括新引入的 Collector 类。同时我还会为大家介绍方法引用, 它可以帮助大家在 Lambda 表达式中轻松使用已有代码。编写大量使用集合类的代码时, 使用方法引用能让程序员获得丰厚的回报。本章还会涉及集合类的一些更高级的主题,比如流中元素的顺序,以及一些有用的 API。

主要内容如下:

  • 5.1 方法引用
  • 5.2 元素顺序
  • 5.3 使用收集器(重点)
  • 5.4 一些细节
  • 5.5 要点回顾

5.1 方法引用

读者可能已经发现,Lambda 表达式有一个常见的用法:Lambda 表达式经常调用参数。比如想得到艺术家的姓名,Lambda 的表达式如下:
artist -> artist.getName()

这种用法如此普遍,因此 Java8 为其提供了一个简写语法,叫作 方法引用,帮助程序员重用已有方法。用方法引用重写上面的 Lambda 表达式,代码如下:
Artist::getName

标准语法为 Classname::methodName。需要注意的是,虽然这是一个方法,但不需要在后面加括号,因为这里并不会立即调用该方法。我们只是提供了和 Lambda 表达式等价的一种结构,在需要时才会调用。凡是使用 Lambda 表达式的地方,就可以使用方法引用。


构造函数也有同样的缩写形式,如果你想使用 Lambda 表达式创建一个 Artist 对象,可能会写出如下代码:

(name, nationality) -> new Artist(name, nationality)

使用方法引用,上述代码可写为:

Artist::new

这段代码不仅比原来的代码短,而且更易阅读。Artist::new 立刻告诉程序员这是在创建一个 Artist 对象,程序员无需看完整行代码就能弄明白代码的意图。另一个要注意的地方是:方法引用自动支持多个参数,前提是选对了正确的函数接口。

还可以用这种方式创建数组,下面的代码创建了一个字符串型的数组:

String[]::new

5.2 元素顺序

关于集合类的内容一个尚未提及的是:流中的元素以何种顺序排列。读者可能知道,一些集合类型中的元素是按顺序排列的,比如 List;而另一些则是无序的,比如 HashSet。增加了流操作后,顺序问题变得更加复杂。

直观上看,流是有序的,因为流中的元素都是按顺序处理的。这种顺序称为 出现顺序。出现顺序的定义依赖于数据源和对流的操作。

在一个有序集合中创建一个流时,流中的元素就按出现顺序排列,因此,例 5-1 中的代码总是可以通过。

// 例 5-1 顺序测试永远通过
List<Integer> numbers = asList(1, 2, 3, 4);  
List<Integer> sameOrder = numbers.stream()
                                 .collect(toList());
assertEquals(numbers, sameOrder);

如果集合本身就是无序的,由此生成的流也是无序的。HashSet 就是一种无序的集合,因此不能保证例 5-2 所示的程序每次都通过。

 // 例 5-2 顺序测试不能保证每次通过
 Set<Integer> numbers = new HashSet<>(asList(4, 3, 2, 1));
 List<Integer> sameOrder = numbers.stream()
                                  .collect(toList());
// 该断言有时会失败
assertEquals(asList(4, 3, 2, 1), sameOrder);

流的目的不仅是在集合类之间做转换,而且同时提供了一组处理数据的通用操作。有些集合本身是无序的,但这些操作有时会产生顺序, 试看例 5-3 中的代码。

// 例 5-3 生成出现顺序
Set<Integer> numbers = new HashSet<>(asList(4, 3, 2, 1));
List<Integer> sameOrder = numbers.stream()
                                  .sorted()
                                  .collect(toList());
assertEquals(asList(1, 2, 3, 4), sameOrder);

一些中间操作会产生顺序,比如对值做映射时,映射后的值是有序的,这种顺序就会保留下来。如果进来的流是无序的,出去的流也是无序的。看一下例 5-4 所示代码,我们只能断言 HashSet 中含有某元素,但对其顺序不能作出任何假设,因为 HashSet 是无序的,使用了映射操作后,得到的集合仍然是无序的。

// 例 5-4 本例中关于顺序的假设永远是正确的
List<Integer> numbers = asList(1, 2, 3, 4);
List<Integer> stillOrdered = numbers.stream()
                                    .map(x -> x + 1)
                                    .collect(toList());
// 顺序得到了保留
assertEquals(asList(2, 3, 4, 5), stillOrdered);
Set<Integer> unordered = new HashSet<>(numbers);
List<Integer> stillUnordered = unordered.stream()
                                        .map(x -> x + 1)
                                        .collect(toList());
                                                                      
// 顺序得不到保证 
assertThat(stillUnordered, hasItem(2));
assertThat(stillUnordered, hasItem(3));
assertThat(stillUnordered, hasItem(4));
assertThat(stillUnordered, hasItem(5));

一些操作在有序的流上开销更大,调用 unordered 方法消除这种顺序就能解决该问题。但大多数操作都是在有序流上效率更高,比如 filtermapreduce等。

这会带来一些意想不到的结果,比如使用并行流时,forEach 方法不能保证元素是按顺序处理的 (第 6 章会详细讨论这些内容)。如果需要保证按顺序处理,应该使用 forEachOrdered 方法,它才是你保证顺序的朋友。

5.3 使用收集器

前面我们使用过 collect(toList()),在流中生成列表。显然,List 是最常用的数据结构,但有时候我们希望从流生成其它数据结构,比如 MapSet,或者你希望定制一个类将你想要的东西抽象出来。

前面已经讲过,仅凭流上方法的返回值,就能判断出这是否是一个及早求值的操作。reduce操作就是一个很好的例子,但有时我们希望能做得更多。

这就是收集器,一种通用的、从流生成复杂值的结构。只要将它传给 collect 方法,所有的流就都可以使用它了。

标准类库已经提供了一些有用的收集器,让我们先来看看。本章示例代码中的收集器都是 从java.util.stream.Collectors类中静态导入的。


5.3.1 转换成其他集合

有一些收集器可以生成其它集合。比如前面已经见过的 toList,生成了 java.util.List 类的实例。还有 toSettoCollection,分别生成 SetCollection 类的实例。到目前为止, 我已经讲了很多流上的链式操作,但总有一些时候&#x

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值