Java 利用比较器实现排序

Java 利用比较器实现排序,用户希望实现对象的排序,使用传入 ComparatorStream.sorted 方法,Comparator 既可以通过 lambda 表达式实现,也可以使用 Comparator 接口定义的某种 comparing 方法生成。

Java 利用比较器实现排序 问题描述

用户希望实现对象的排序。

Java 利用比较器实现排序 解决方案

使用传入 ComparatorStream.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 类定义的各种静态方法,就能写出更简单的代码。在本例中,comparingnaturalOrder 方法已被静态导入。
即便没有实现 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 方法,生成复杂的排序变得易如反掌。

赞(2)

评论 抢沙发

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址

Java 实例