Previously on OOP:
University project can be implemented using Java Generics.
其次,本黄鸭还增加了一道附加题:在University类的、存放所有学生信息的include Map中,要求按照学生学号排序。实现这个需求,只要把include的object的类型从HashMap改成TreeMap就行了,不需要在Student类中添加implements Comparable Interface。如果是把HashMap改成TreeList / TreeSet,才有Comparable出场的机会。
listAttendees()函数的返回值是参加某课程的、所有学生的信息。为了进一步地提高难度系数,本黄鸭决定再追加一项需求:学生要按照学号排序。
University类中的listAttendees()函数一切照旧。先用课程编号从存放所有课程信息的Map中取出对应课程的object reference,再用该object reference调用Course类中的attendees()函数。
Furthermore, the implementation to get the study plan for a single student is similar to listAttendees() method.
在没有追加需求的情况下,attendees()函数的编写如下:
先创建一个空的StringBuffer,然后用循环结构把一条条Student信息转化为字符串,append在StringBuffer的后面,最后把StringBuffer转化为String,作为返回值返回。
当然,创建的是一个空的String,再使用加号把各种字符串concatenate在一起,也是完全可以的。
另外,想要实现追加的需求,我们可以有以下几种想法:
(1)一边concatenate String / append StringBuffer,一边排序。
请参考一下选择排序,再把这种想法转化为代码。假设一共有N个学生参加这门课程,会有一个循环遍历N遍存放所有学生的数据结构。第一次遍历的任务是找到学号最小的学生,第二次是找到比第一次大且学号最小的学生,以此类推,直到找不到比上次的学号还要大的学生,也就是所有学生都被放到String / StringBuffer中了。
以上算法的复杂度也和选择排序相同,是O(N^2),属于时间复杂度相对比较高的。
(2)在concatenate String / append StringBuffer之后,再排序。然而,目前本黄鸭没有想到用哪一种算法来实现这个想法,所以,请各位宝宝们果断放弃。
(3)在concatenate String / append StringBuffer之前,先排序。本黄鸭觉得这种是最容易实现的,因为总共有五种方法可以实现。
Solution 1
调用Collections类中的sort()函数来排序。因为dotted notation前面是类的名称,而不是object reference,所以我们有理由相信sort()函数是一个static的函数。
排序的前提是比较,所以我们需要定义两个Student类型的objects之间的大小关系。又因为Course类中没有比较Student objects大小的代码,只能到Student类中去寻找,所以本段代码必须和Student implements Comparable Interface一起使用。先在Student类的声明行加上implements Comparable ,再override compareTo()函数。
Solution 2
本段代码的Student类不需要implements Comparable Interface,因为这里已经选用了Comparator Interface。
虽然Comparable和Comparator都是Interfaces,而且里面都只有一个叫做compareTo()的method需要被子类overridden,但是只有Comparator是functional Interface,这是因为Comparable的compareTo()和当前object有关,而Comparator的compareTo()和当前object无关。
既然只有Comparator是functional Interface,那么也只有它能使用Lambda expression。然而,本段代码中只用到了functional Interface的性质,没有使用Lambda expression。
Collections.sort()函数收到的第一个参数是students,即待排序的数据结构。第二个参数是一个anonymous inner class。
本黄鸭曾经说过,一个实例的类型不能直接是某个Interface类型的,必须要先创建该Interface的子类,才能创建实例。所以第二个参数不能理解为调用构造函数,而是anonymous inner class。
Anonymous,翻译为匿名,顾名思义,在类的声明部分没有Comparator Interface的子类的名称,只有关键字“new” + Comparator类型。
在类的声明后面有一对花括号,里面写的是这个类的内容,只有一个重载的compareTo()函数。如果把这个函数的返回值改成相反数,会从sort in ascending order变为sort in descending order。
Solution 3
现在是Comparator functional Interface的Lambda expression的写法。s1和s2是两个Student类型的临时变量。
Solution 4
前面三种方法分别是实现比较的两种方法:Comparable Interface,以及Comparator Interface。而本段代码使用的是第三种方法:Comparator Interface中的一个comparing() static method。这个函数的需要的参数是key extractor,即用来作为比较标准的那个attribute,也就是学生的学号。
comparing()函数不需要我们从零开始自己编写一个,只要会调用就可以了。有兴趣的宝宝们可以稍微看一下comparing()的函数体:
Solution 5
最后一种方法和Solution 4一样,都用的是comparing() static method。区别在于作为参数的key extractor没有用Lambda expression来写,而采用了method reference。
本黄鸭不得不再强调一遍Lambda expression表示调用和method reference之间的区别:
(1)写法不同。
Lambda expression的左边是parameters,即可以指明类型,也可以不指明的临时object references。中间用“->”连接。右边是expression,即使用object references调用的函数。
Method reference的左边是类的名称。中间用“::”连接。右边是方法的名称,没有括号。
(2)能连续调用函数的个数不同。
Lambda expression右边的表达式可以包含cascaded, combined, and multiple dotted notations。而Method reference右边只能有一个method name。
欢迎使用本黄鸭编写的小程序~
微信公众号二维码:
领取专属 10元无门槛券
私享最新 技术干货