引言

在Java开发中,按字母顺序排序列表是常见需求。本文将系统探讨多种排序方法,从基础的Collections类到高级的RuleBasedCollator,覆盖各种实际场景:

  • 基础排序:自然顺序与逆序
  • 自定义排序:处理大小写混合、特殊字符
  • 现代API:Stream排序与TreeSet应用
  • 国际化处理:本地化排序与重音字符处理
  • 高级技巧:基于规则的排序器

每种方法都配有完整代码示例,助你快速掌握实战技巧。

使用Collections类排序

Collections类提供了静态sort()方法,支持自然排序和自定义比较器两种模式。

自然顺序/字典序排序

首先定义测试数据:

private static List<String> INPUT_NAMES = Arrays.asList("john", "mike", "usmon", "ken", "harry");

使用Collections.sort()进行字典序排序:

@Test
void givenListOfStrings_whenUsingCollections_thenListIsSorted() {
    Collections.sort(INPUT_NAMES);
    assertThat(INPUT_NAMES).isEqualTo(EXPECTED_NATURAL_ORDER);
}

预期结果:

private static List<String> EXPECTED_NATURAL_ORDER = Arrays.asList("harry", "john", "ken", "mike", "usmon");

关键点

  • 列表按元素自然升序排列
  • 要求元素实现Comparable接口(String已实现)
  • 原地修改列表,无需返回新列表

逆序排序

通过自定义比较器实现逆序排序:

Comparator<String> reverseComparator = (first, second) -> second.compareTo(first);

或使用内置方法:

Comparator<String> reverseComparator = Comparator.reverseOrder();

完整测试用例:

@Test
void givenListOfStrings_whenUsingCollections_thenListIsSortedInReverse() {
    Comparator<String> reverseComparator = Comparator.reverseOrder();
    Collections.sort(INPUT_NAMES, reverseComparator); 
    assertThat(INPUT_NAMES).isEqualTo(EXPECTED_REVERSE_ORDER); 
}

预期结果:

private static List<String> EXPECTED_REVERSE_ORDER = Arrays.asList("usmon", "mike", "ken", "john", "harry");

注意:由于String是final类,无法通过继承重写compareTo,必须使用Comparator实现逆序。

使用Comparator实现自定义排序

当需要特殊排序逻辑时,Comparator接口是最佳选择。

处理大小写混合列表

测试用例展示大小写敏感问题:

@Test
void givenListOfStringsWithUpperAndLowerCaseMixed_whenCustomComparator_thenListIsSortedCorrectly() {
    List<String> movieNames = Arrays.asList("amazing SpiderMan", "Godzilla", "Sing", "Minions");
    List<String> naturalSortOrder = Arrays.asList("Godzilla", "Minions", "Sing", "amazing SpiderMan");
    List<String> comparatorSortOrder = Arrays.asList("amazing SpiderMan", "Godzilla", "Minions", "Sing");

    Collections.sort(movieNames);
    assertThat(movieNames).isEqualTo(naturalSortOrder);

    Collections.sort(movieNames, Comparator.comparing(s -> s.toLowerCase()));
    assertThat(movieNames).isEqualTo(comparatorSortOrder);
}

问题与解决

  • 默认排序:["Godzilla", "Minions", "Sing", "amazing SpiderMan"](错误)
  • 自定义比较器:["amazing SpiderMan", "Godzilla", "Minions", "Sing"](正确)
  • 关键技巧:使用String::toLowerCase作为排序键提取器

处理特殊字符

要求@开头的字符串排在最后:

@Test
void givenListOfStringsIncludingSomeWithSpecialCharacter_whenCustomComparator_thenListIsSortedWithSpecialCharacterLast() {
    List<String> listWithSpecialCharacters = Arrays.asList("@laska", "blah", "jo", "@sk", "foo");

    List<String> sortedNaturalOrder = Arrays.asList("@laska", "@sk", "blah", "foo", "jo");
    List<String> sortedSpecialCharacterLast = Arrays.asList("blah", "foo", "jo", "@laska", "@sk");

    Collections.sort(listWithSpecialCharacters);
    assertThat(listWithSpecialCharacters).isEqualTo(sortedNaturalOrder);

    Comparator<String> specialSignComparator = Comparator.<String, Boolean>comparing(s -> s.startsWith("@"));
    Comparator<String> specialCharacterComparator = specialSignComparator.thenComparing(Comparator.naturalOrder());

    listWithSpecialCharacters.sort(specialCharacterComparator);
    assertThat(listWithSpecialCharacters).isEqualTo(sortedSpecialCharacterLast);
}

实现要点

  • 默认排序:["@laska", "@sk", "blah", "foo", "jo"](不符合需求)
  • 链式比较器:先按是否@开头分组,组内自然排序
  • 最终结果:["blah", "foo", "jo", "@laska", "@sk"]

使用Stream排序

Java 8 Stream API提供函数式排序方案。

自然顺序排序

@Test
void givenListOfStrings_whenSortWithStreams_thenListIsSortedInNaturalOrder() {
    List<String> sortedList = INPUT_NAMES.stream()
      .sorted()
      .collect(Collectors.toList());

    assertThat(sortedList).isEqualTo(EXPECTED_NATURAL_ORDER);
}

特性

  • sorted()返回按自然顺序排序的流
  • 要求元素实现Comparable

逆序排序

使用Lambda定义比较器:

@Test
void givenListOfStrings_whenSortWithStreamsUsingComparator_thenListIsSortedInReverseOrder() {
    List<String> sortedList = INPUT_NAMES.stream()
      .sorted((element1, element2) -> element2.compareTo(element1))
      .collect(Collectors.toList());
    assertThat(sortedList).isEqualTo(EXPECTED_REVERSE_ORDER);
}

或使用内置比较器:

Comparator<String> reverseOrderComparator = Comparator.reverseOrder();
List<String> sortedList = INPUT_NAMES.stream()
  .sorted(reverseOrder)
  .collect(Collectors.toList());

使用TreeSet排序

TreeSet基于红黑树实现,自动维护元素排序:

@Test
void givenNames_whenUsingTreeSet_thenListIsSorted() {
    SortedSet<String> sortedSet = new TreeSet<>(INPUT_NAMES);
    List<String> sortedList = new ArrayList<>(sortedSet);
    assertThat(sortedList).isEqualTo(EXPECTED_NATURAL_ORDER);
}

注意事项

  • 自动去重,会丢失重复元素
  • 仅适用于无重复值的场景

使用List的sort方法

List接口自带的sort()方法更简洁:

@Test
void givenListOfStrings_whenSortOnList_thenListIsSorted() {
    INPUT_NAMES.sort(Comparator.reverseOrder());
    assertThat(INPUT_NAMES).isEqualTo(EXPECTED_REVERSE_ORDER);
}

优势:直接在原列表操作,无需额外工具类。

本地化敏感排序

不同语言的排序规则差异显著,例如西班牙语:

List<String> accentedStrings = Arrays.asList("único", "árbol", "cosas", "fútbol");

默认排序结果:["cosas", "fútbol", "árbol", "único"](错误)

使用Collator处理本地化排序:

Collator esCollator = Collator.getInstance(new Locale("es"));
accentedStrings.sort((s1, s2) -> {
    return esCollator.compare(s1, s2);
});

正确结果:["árbol", "cosas", "fútbol", "único"]

完整测试用例:

@Test
void givenListOfStringsWithAccent_whenSortWithTheCollator_thenListIsSorted() {
    List<String> accentedStrings = Arrays.asList("único", "árbol", "cosas", "fútbol");
    List<String> sortedNaturalOrder = Arrays.asList("cosas", "fútbol", "árbol", "único");
    List<String> sortedLocaleSensitive = Arrays.asList("árbol", "cosas", "fútbol", "único");

    Collections.sort(accentedStrings);
    assertThat(accentedStrings).isEqualTo(sortedNaturalOrder);

    Collator esCollator = Collator.getInstance(new Locale("es"));

    accentedStrings.sort((s1, s2) -> {
        return esCollator.compare(s1, s2);
    });

    assertThat(accentedStrings).isEqualTo(sortedLocaleSensitive);
}

处理重音字符排序

Unicode中重音字符有多种编码方式,需特殊处理。

标准化后排序

使用Normalizer规范化字符:

Collections.sort(accentedStrings, (o1, o2) -> {
    o1 = Normalizer.normalize(o1, Normalizer.Form.NFD);
    o2 = Normalizer.normalize(o2, Normalizer.Form.NFD);
    return o1.compareTo(o2);
});

结果:["árbol", "cosas", "fútbol", "único"]

关键NFD形式将重音字符分解为基本字符+修饰符。

移除重音后排序

使用Apache Commons Lang的StringUtils

@Test
void givenListOfStrinsWithAccent_whenComparatorWithNormalizer_thenListIsNormalizedAndSorted() {
    List<String> accentedStrings = Arrays.asList("único", "árbol", "cosas", "fútbol");

    List<String> naturalOrderSorted = Arrays.asList("cosas", "fútbol", "árbol", "único");
    List<String> stripAccentSorted = Arrays.asList("árbol","cosas", "fútbol","único");

    Collections.sort(accentedStrings);
    assertThat(accentedStrings).isEqualTo(naturalOrderSorted);
    Collections.sort(accentedStrings, Comparator.comparing(input -> StringUtils.stripAccents(input)));
    assertThat(accentedStrings).isEqualTo(stripAccentSorted); 
}

使用基于规则的排序器

RuleBasedCollator支持自定义排序规则:

@Test
void givenListofStrings_whenUsingRuleBasedCollator_thenListIsSortedUsingRuleBasedCollator() throws ParseException {
    List<String> movieNames = Arrays.asList(
      "Godzilla","AmazingSpiderMan","Smurfs", "Minions");

    List<String> naturalOrderExpected = Arrays.asList(
      "AmazingSpiderMan", "Godzilla", "Minions", "Smurfs");
    Collections.sort(movieNames);

    List<String> rulesBasedExpected = Arrays.asList(
      "Smurfs", "Minions", "AmazingSpiderMan", "Godzilla");

    assertThat(movieNames).isEqualTo(naturalOrderExpected);

    String rule = "< s, S < m, M < a, A < g, G";

    RuleBasedCollator rulesCollator = new RuleBasedCollator(rule);
    movieNames.sort(rulesCollator);

    assertThat(movieNames).isEqualTo(rulesBasedExpected);
}

规则解析

  • < 表示小于关系
  • 规则顺序:s < m < a < g
  • 结果:["Smurfs", "Minions", "AmazingSpiderMan", "Godzilla"]

注意事项

  • 规则语法错误会抛出ParseException
  • 实现了Comparator接口,可直接用于排序

总结

本文系统梳理了Java中列表字母排序的多种方案:

方法 适用场景 优势 局限性
Collections.sort() 基础排序 简单直接 功能单一
Comparator 自定义排序 灵活控制逻辑 需手动实现
Stream API 函数式处理 链式操作 依赖Java 8+
TreeSet 自动去重 自动维护顺序 丢失重复值
List.sort() 原地排序 语法简洁 仅限List接口
Collator 本地化排序 支持多语言 需指定Locale
RuleBasedCollator 复杂规则 高度定制化 规则定义复杂

实战建议

  1. 简单场景优先用Collections.sort()
  2. 特殊需求通过Comparator定制
  3. 国际化场景必用Collator
  4. 复杂规则考虑RuleBasedCollator

掌握这些技巧,轻松应对各种排序需求!