Previously on OOP:
toSet() collector can eliminate duplicated elements. A Lambda expression can be used as a predicate of filter() and map() methods. The concept of flatMap() method is to extract a column composed of Collections and re-organize them into a new Stream.
map()函数只能把Stream转化为Stream类型,但是用mapToInt()函数可以转化为Stream类型。Stream里面的数据类型是wrapper class还是primitive type,对于max()函数是有区别的。如果是primitive type,max()函数的predicate可以为空;反之则要在predicate中指明两个Stream的实例的比较大小的方法,如下所示:
这个Lambda expression应该是Comparator functional Interface的子类的实例,重载的是compareTo()函数。另外,定义某类的实例之间的大小的常用方法有:
(1)implements Comparable Interface:这个类不是一个functional Interface,也就是里面包含不止一个需要重载的函数;或者需要重载的函数不仅仅和传入的参数有关,还和实例本身有关。Comparable Interface的情况属于后者。
(2)implements Comparator Interface:这是一个functional Interface,所以适合用Lambda expression来编写。
(3)调用comparing()函数:这个函数在Comparator Interface中。因为是一个静态函数,所以在implements Comparator Interface的时候不需要重载它。
最后,max()函数的返回值应该是Optional类型的,即可能没有返回值。本黄鸭不得不再重申一遍“没有返回值”和“返回值是void”的区别,像constructor那样的叫做没有返回值。对于Optional类型,我们还可以调用.isPresent(), .ifPresent(), get(), orElse(),等等函数来分类讨论。本段代码中的orElse()函数表示在max()函数没有返回值的时候,把值设为。
上一个requirement是要比较学生姓氏的长度,现在是要比较学生实例之间的大小,比较的标准是他们姓氏的长度,姓氏越长就越大。所以,本段代码的重点是把Student objects之间大小关系作为max()函数的predicate。用刚才讲过的三种方法都可以实现,现在本黄鸭决定选用comparing()。
comparing()函数的参数是作为比较标准的那个attribute。而attributes一般都是private的,想要取得它们的值只能调用getters。“s”是一个Student类型的临时变量,先调用getLast()函数取得学生的姓氏,顺便把类型转化为String。再调用String类中的length()函数取得学生姓氏的长度,即我们的比较标准。
此外,在本例中,千万不能调用map()或者mapToInt()函数,否则Stream中没有办法包含Student objects。类似地,本段代码中的max()函数的返回值也是Optional,可以继续分类讨论。
有的宝宝为了避开max()函数的Optional类型的返回值,喜欢调用“.sorted().limit(1)”。这种方法是可以实现需求的,但是比起max()来说复杂度提升了很多。所以,在没有明确要求排序的时候,本黄鸭不建议调用sorted()函数。不过这个需求是明确要求排序的,所以非常合适调用sort()函数。
需求中还有一个重点在于“separated by ","”。有的宝宝觉得使用非Stream的方法很好实现:
(1)编写一个for / for each循环,然后在一个StringBuffer的后面不断地append学生姓氏和逗号。顶多再判定一个学生姓氏是不是最后一个,如果是,那么就少append一个逗号即可。
(2)把学生姓氏存放在一个Collection类型的数据结构中,然后调用toString()函数。在运气好的情况下,系统默认的分隔符就是逗号。
既然我们在举Stream的例子,本黄鸭还是要用Stream的方法来实现的。在predefined accumulating collector中,有一个叫做joining()的小众collector,功能是concatenates elements into a new String,参数是separator。那么,我们只要调用joining() collector就可以了。
groupingBy()是一个常用的predefined grouping collector,功能是把Stream整合成一个Map,接收的第一个参数是作为Map的key field的attribute。同样地,attributes一般都是private的,访问只能通过调用getters,所以,groupingBy()的第一个参数会是method reference或者Lambda expression。在本段代码中,Map的key field是学生的性别。
如果groupingBy()没有接收到其他参数,就把key对应的Stream中的数据作为Map的value field。假设还有一个key对应多个数据的情况,那么这个collector会自动把多个数据合并为一个List,存为value field。在本段代码中,除了“Student::getGender”之外没有参数,所以value field应该是Student的实例,或者Student的实例构成的List,用代码来写就是“List”。
本段代码的groupingBy()有了第二个参数,counting()。它本身也是一个predefined collector,功能是统计数据的个数。现在,counting()作为参数,指定了生成Map的value field不是List,而是List再做counting(),即对应Gender的学生的人数。
还有一点需要注意,counting() collector的返回值是Long,不是Integer。这样才使得返回值的类型是:Map。
本黄鸭刚刚才说过,当groupingBy()只有一个参数时,参数指定的是一个attribute,作为生成Map的key field。这个attribute不仅可以用method reference来写,还可以用Lambda expression。本段代码中就采用了后者。
“s”是一个Student类型的临时变量,调用了Student类的enrolledIn()函数,取得courses attribute,即这名学生参加的所有课程的Collection。此时类型变为Collection。接着调用Collection类的size()函数,求这名学生参加所有课程的数量,并作为生成Map的key field。
又因为返回值类型是“Map>”,所以:
(1)要把key field强制转换为“Long”类型
(2)groupingBy()不需要其他参数
在理论部分,本黄鸭还提到过一种接收三个参数的groupingBy() collector,多出来的参数一般是“TreeMap::new”,位置在第二个。这种groupingBy()生成的Map,会按照key field的值排序。
欢迎使用本黄鸭编写的小程序~
微信公众号二维码:
领取专属 10元无门槛券
私享最新 技术干货