Previously on OOP:
toList() collector arranges the elements of Stream into a List,joining()collector arranges the elements into a String, andgroupingBy()into a Map. In addition, we have made examples of max() and sorted() methods.
这是本例中最后一个,也是最复杂的一个需求。我们先来分析一下题目:
(1)函数的返回值是Map>类型的,key field是学生参加的课程的数量,value field是参加这么多课程的学生的列表。在这个Map中,按照key field来排序是非常容易的,只要把groupingBy()的第二个参数写成TreeMap::new就可以了。
groupingBy()的第一个参数是作为比较的标准的那个attribute,用Student类的object reference调用enrolledIn()函数,取到courses attributes,再用dotted notation调用Collection类中的size()函数,求该名学生参加课程的数量。
之所以前面有一个负号,是因为想要在Map中,按照参加课程的数量逆序排列。
此时,在生成的Map的value field中,Student的顺序是插入的顺序,并没有排序。本课程的考试题大致是就是这种难度的,所以各位宝宝们一定要理解到这一步。
接下来还有两个小需求:
(2)每个value field都要排序,标准是学生姓名的首字母。
使用非Stream的方法不难实现这个需求。依次遍历每一个Map中的value field,然后把该value field打开为Stream,调用sorted函数排序,用collector整理成为list。最后,替换掉原先的value field即可。
(3)如果两个学生的姓名首字母相同,并列第一,那么下一名学生的排名是3。这个应该不是很重要,因为返回的Map的value field是List,里面没有包含排序信息的字段。
不过既然是Stream的例子,本黄鸭还是要讨论一下Stream的方法。在执行完groupingBy()之后,就不再是Stream类型了,而是Map类型。为了还能继续用Stream的方法,我们首先要把Map再转换为Stream。
entrySet()函数能把Map中的一组相对应的key field and value field打包成一个entry,使得Map看起来不再是key field和value field分开的,而是一个一个的entries,就像数组那样。
然后调用stream()函数把Map转化为Stream,此时Stream的类型是:Stream>>。
接下来是使用一个自定义的collector把Stream重新整合成一个Map,同时,实现上面两个需求。
第一步,supplier,即创建一个新的容器。这时我们还要继续保证生成的Map是按照单个学生参加的课程数量排序的,所以继续使用TreeMap,因为它能在添加元素的时候自动按照key field的值排序。
第二步,accumulator,即往刚创建的容器里面加东西。Accumulator的类型是BiConsumer,是一个functional Interface,所以可以使用Lambda expression来创建该functional Interface的子类的实例,并重载里面的abstract的函数。
在本段代码中,“r”是一个零时变量,用于计算当前Student实例的名次,如果名叫rank的Map现在为空,那么第一个元素的名次就是1。如果非空,那么就要加上number of elements in the last entry。
因为是往TreeMap类的supplier中加入元素,所以使用的函数应该是put()。Key field是计算后的名次,value field是List。
第三步,combiner,如果前面有调用parallel()并行处理,那么在这里需要定义如何把多线程的结果合并成为一个。这个程序对于本课程来说过于复杂,所以本黄鸭不做分析,各位宝宝们看看就好:
最后一步,finishier,把含有全部List元素的Map返回回去。在本段代码中是implicitly存在的。
综上所述,使用Stream的方法来实现后面两个需求是非常复杂的,强烈推荐使用非Stream的方法,或者两者结合。
在本例的最后,我们可以编写一个简单的main函数(或者test cases)来测试一下每个函数的运行结果是不是和需求吻合。
欢迎使用本黄鸭编写的小程序~
微信公众号二维码:
领取专属 10元无门槛券
私享最新 技术干货