面向对象的编程-Application 31

Previously on OOP:

A more comprehensive example on students has been made. There are two auxiliary classes: Course and Student. The latter one is composed of both student identifications and courses enrolled. In the main facade,StudentStreams, we have implemented two requirements.

前面两个requirements都是用filter() + collect()组合来实现的,而现在我们的需求要取得学生的last names,所以filter()函数下岗了,换map()函数上岗。也就是说,这个requirement是由map() + collect()组合实现的。

另外,在collect()函数中,调用的predefined collector不是toList(),而是toSet(),这是因为需求中还指明要unique last names。这个“unique”是重点,有些不太懂英语的宝宝们一看到这个字就马上把所有相关的数据结构都换成Map,比如这里的collect()函数就选用grouping collector中的一个,理由是本黄鸭讲过Map的key field的值都必须是独一无二的。但是,请各位宝宝们一定要仔细地看以下几个细节:

(1)函数返回值是“Collection”类型的。如果在termination process中选用了某一个grouping collector,那么得到的只能是Map类型的,还要继续加工才能作为返回值。

(2)得到的Map类型中的“XXXX”是什么类型呢?

一般情况下应该是Student,但是这里不可能,因为Stream经过map()函数的处理之后,已经变成了Stream类型,不包含Student。

那么能不能是String类型呢?也不太可能,否则last name不仅要在key field存,而且还要在value field中存。

所以,不要一看到“unique”就马上毫不犹豫地选择Map,而是除了Map之外还要继续想起下面两件事:

(1)Set有一个很好的性质是“互异性”,也就是集合会自动踢掉重复的元素。而且Set是“Collection”的子类,可以直接作为返回值。于是,实现本requirement最好的collector是toSet()。

(2)distinct()函数能去除重复的元素,代码如下所示:

先用map()函数取出学生的last names,这一步把Stream转化为Stream类型,即里面存放的数据变成:Smith, Johnson, Rossi, Ferrari, Wang, Li。

然后再调用一个map函数,可以求每一个last name的长度,把Stream转化为Stream类型,即里面存放的数据变成:5, 7, 5, 7, 4, 2。

最后使用toList() collector,把last name的长度整理在一个List中作为返回值。

本段代码中的两个map()函数可以浓缩成为一个,代码如下所示:

Method reference只能调用一个函数,而Lambda expression结合dotted notation能调用多个函数。能实现combined dotted notation的前是中间每个函数的返回值都是恰当的object reference。也就是说getFirst()函数的返回值应该是一个String类型的object reference,我们才能用这个object reference调用String类中的length()方法。

另外,在多次调用之后,编译器可能会搞不清楚当前Stream中存放的数据类型,从而报出编译错误。这时就需要开发者在临时变量的前面加上explicit cast。

filter()函数的Predicate也可以由Lambda expression来充当。“s.enrolledIn()”执行之后,Stream中的数据类型全部变成Collection类的object references。整个Stream的类型记作:Stream>。

接着,我们用这些object references调用了Collection类中的size()函数,求每个学生的“Collection”的大小。如果大于2,则通过了filter()函数的筛选,保留在Stream中,反之则被踢掉。

在本段代码中,万众瞩目的flatMap()函数终于粉墨登场了。flatMap()函数的理解一直是重灾区,本黄鸭要结合本例来分析一下。

看StudentStreams类的constructor,我们可以知道List students含有的信息如下表所示:

表1

现在的需求是要找齐所有的courses。

第一种方案是在StudentStreams类中增加一个attributes,名称叫做courses,类型是List。在constructor中每创建一门课程,就加到courses attribute里面去。这样,这个requirement实现起来就非常容易了,直接返回“this.courses”即可,完美地避开了flatMap()的雷区。

对于实在不会用flatMap()的宝宝们,强烈推荐这种方案。当然,这里还有一个小瑕疵,那就是当A课程没有人选的时候,会保留在返回值中。遇到这种棘手的题目也不是非flatMap()不可,还可以在Student类中编写一个函数,来检验里面是否含有课程A;当所有人都不含有课程A时,就踢出去。

第二种方案就是从StudentStreams类的students attribute出发,使用flatMap()函数,整理出所有的courses。在“表1”中,“Courses Enrollment Information”这一列含有课程信息,其他列都是学生个人信息。所以,首先要用map()函数取出“Courses Enrollment Information”这一列。

取出之后,整个Stream的类型变成:Stream>。这不是我们想要的,我们想要的是Stream类型的,因为:

(1)本黄鸭从来没有教过三层数据结构的增、删、改、查。

(2)只有这种类型才能通过一个collector整理为Collection类型,并返回。

这时,调用flatMap()函数能做到我们想要的。它先把每一个Collection展开成新的Streams,再把六个小的Streams合并起来,得到一个新的Stream,类型是Stream。

flatMap()函数既能像上面一段代码一样编写;也能拆分成两个函数,map()和flatMap(),编写方式如下:

最后,本例采用的collector是toSet(),可以自动去除掉重复的课程。

这个requirement只比上一个多了一个步骤,就是用map()函数取得每一个Course的名称,把Stream转化为Stream。

欢迎使用本黄鸭编写的小程序~

微信公众号二维码:

  • 发表于:
  • 原文链接https://kuaibao.qq.com/s/20181107G1FK4900?refer=cp_1026
  • 腾讯「云+社区」是腾讯内容开放平台帐号(企鹅号)传播渠道之一,根据《腾讯内容开放平台服务协议》转载发布内容。
  • 如有侵权,请联系 yunjia_community@tencent.com 删除。

扫码关注云+社区

领取腾讯云代金券