Java 利用比较器实现排序,用户希望实现对象的排序,使用传入 Comparator
的 Stream.sorted
方法,Comparator
既可以通过 lambda 表达式实现,也可以使用 Comparator
接口定义的某种 comparing
方法生成。
Java 利用比较器实现排序 问题描述
用户希望实现对象的排序。
Java 利用比较器实现排序 解决方案
使用传入 Comparator
的 Stream.sorted
方法,Comparator
既可以通过 lambda 表达式实现,也可以使用 Comparator
接口定义的某种 comparing
方法生成。
Java 利用比较器实现排序 具体实例
Stream.sorted
方法根据类的自然顺序(natural ordering)生成一个新的排序流,自然顺序是通过实现 java.util.Comparable
接口来指定的。
如例 4-1 所示,我们对字符串集合进行排序。
例 4-1 根据字典序(自然顺序)对字符串排序
private List<String> sampleStrings =
Arrays.asList("this", "is", "a", "list", "of", "strings");
public List<String> defaultSort() {
Collections.sort(sampleStrings); ➊
return sampleStrings;
}
public List<String> defaultSortUsingStreams() {
return sampleStrings.stream()
.sorted() ➋
.collect(Collectors.toList());
}
❶ 默认排序(Java 7 及之前的版本)
❷ 默认排序(Java 8 及之后的版本)
从 Java 1.2 引入集合框架(collections framework)开始,工具类 Collections
就已存在。Collections
类定义的静态方法 sort
传入 List
作为参数,但返回 void
。这种排序是破坏性的,会修改所提供的集合。换言之,Collections.sort
方法不符合 Java 8 所倡导的将不可变性(immutability)置于首要位置的函数式编程原则。
Java 8 采用 Stream.sorted
方法实现相同的排序,但不对原始集合进行修改,而是生成一个新的流。在例 8-1 中,完成集合的排序后,程序根据类的自然顺序对返回列表进行排序。对于字符串,自然顺序是字典序(lexicographic order);如果所有字符串均为小写,自然顺序就相当于字母顺序(alphabetical order),从本例可以观察到这一点。
如果希望以其他方式排序字符串,可以使用 sorted
方法的重载形式,传入 Comparator
作为参数。
例 4-2 采用两种不同的方式,根据长度对字符串进行排序。
例 4-2 根据长度对字符串排序
public List<String> lengthSortUsingSorted() {
return sampleStrings.stream()
.sorted((s1, s2) -> s1.length() - s2.length()) ➊
.collect(toList());
}
public List<String> lengthSortUsingComparator() {
return sampleStrings.stream()
.sorted(Comparator.comparingInt(String::length)) ➋
.collect(toList());
}
❶ 使用 lambda 表达式作为 Comparator
,根据长度进行排序
❷ 使用 Comparator.comparingInt
方法
sorted
方法的参数为 java.util.Comparator
,它是一种函数式接口。对于第一个方法 lengthSortUsingSorted
,所提供的 lambda 表达式用于实现 Comparator.compare
方法。在 Java 7 及之前的版本中,实现通常由匿名内部类提供,而本例仅需要一个 lambda 表达式。
Java 8 引入
sort(Comparator)
作为List
接口的默认实例方法,它相当于Collections
类的sort(List, Comparator)
方法。由于两种方法返回void
且都是破坏性的,本节讨论的Stream.sorted(Comparator)
方法(返回一个新的排序流)仍然是首选方案。
第二个方法 lengthSortUsingComparator
利用了 Comparator
接口新增的某种静态方法。comparingInt
方法传入一个 ToIntFunction
类型的参数(文档称之为 keyExtractor
),用于将字符串转换为整型数据,并生成一个使用该 keyExtractor
对集合进行排序的 Comparator
。
Comparator
接口新增的各种默认方法极为有用。虽然不难写出一个根据长度进行排序的 Comparator
,但如果需要对多个字段排序,代码可能会变得很复杂。例如,我们希望根据长度对字符串排序,如果长度相同则按字母顺序排序。如例 4-3 所示,采用 Comparator
接口提供的默认和静态方法很容易就能解决这个问题。
例 4-3 根据长度对字符串排序,长度相同则按字母顺序排序
public List<String> lengthSortThenAlphaSort() {
return sampleStrings.stream()
.sorted(comparing(String::length) ➊
.thenComparing(naturalOrder()))
.collect(toList());
}
➊ 根据长度对字符串排序,长度相同则按字母顺序排序
Comparator
接口定义了一个称为 thenComparing
的默认方法。与 comparing
方法类似,thenComparing
方法也传入 Function
作为参数,文档同样将其称为 keyExtractor
。如果将 thenComparing
方法链接到 comparing
方法会返回 Comparator
,它首先比较第一个数量,如果相同则比较第二个数量,以此类推。
静态导入通常使代码更易于阅读。一旦熟悉 Comparator
接口和 Collectors
类定义的各种静态方法,就能写出更简单的代码。在本例中,comparing
和 naturalOrder
方法已被静态导入。
即便没有实现 Comparable
接口,上述方案也适用于任何类。如例 4-4 所示,我们定义一个描述高尔夫球手的 Golfer
类。
例 4-4 描述高尔夫球手的
Golfer
类
public class Golfer {
private String first;
private String last;
private int score;
// 其他方法
}
为了创建一个高尔夫锦标赛排行榜,可以依次根据各个球手的得分、姓氏、名字进行排序。例 4-5 显示了如何实现高尔夫球手的排序。
例 4-5 对高尔夫球手排序
private List<Golfer> golfers = Arrays.asList(
new Golfer("Jack", "Nicklaus", 68),
new Golfer("Tiger", "Woods", 70),
new Golfer("Tom", "Watson", 70),
new Golfer("Ty", "Webb", 68),
new Golfer("Bubba", "Watson", 70)
);
public List<Golfer> sortByScoreThenLastThenFirst() {
return golfers.stream()
.sorted(comparingInt(Golfer::getScore)
.thenComparing(Golfer::getLast)
.thenComparing(Golfer::getFirst))
.collect(toList());
}
调用 sortByScoreThenLastThenFirst
方法,输出如例 4-6 所示。
例 4-6 对高尔夫球手排序后的结果
Golfer{first='Jack', last='Nicklaus', score=68}
Golfer{first='Ty', last='Webb', score=68}
Golfer{first='Bubba', last='Watson', score=70}
Golfer{first='Tom', last='Watson', score=70}
Golfer{first='Tiger', last='Woods', score=70}
本例首先根据得分对各个球手进行排序,因此 Jack Nicklaus 和 Ty Webb1 排在 Tiger Woods、Bubba Watson 与 Tom Watson 之前。得分相同时根据姓氏进行排序,因此 Jack Nicklaus 排在 Ty Webb 之前,Bubba Watson 和 Tom Watson 排在 Tiger Woods 之前。如果得分和姓氏都相同,则根据名字进行排序,因此 Bubba Watson 排在 Tom Watson 之前。
1当然,Ty Webb 是电影《小小球童》中的角色。Judge Smails 问道:“今天打得怎么样?”Ty Webb 回答:“我没有计分。”Smails 继续问道:“那你怎么衡量自己与其他球手的水平呢?”Webb 回答:“身高。”根据身高进行排序留给读者作为简单的练习。
借由 Comparator
接口定义的默认和静态方法以及 Stream
接口新增的 sorted
方法,生成复杂的排序变得易如反掌。