在Java中按字母顺序对一个列表进行排序
1.绪论
在本教程中,我们将探讨在Java中按字母顺序对列表进行排序的各种方法。
首先,我们将从Collections类开始,然后使用Comparator接口。我们还将使用List的API来按字母顺序排序,然后是流,最后使用TreeSet.。
此外,我们将扩展我们的例子,以探索几种不同的情况,包括基于特定区域的列表排序,排序重音列表,以及使用RuleBasedCollator 来定义我们的自定义排序规则。
2.使用Collections类进行排序
首先,让我们看看如何使用Collections类来对一个列表进行排序。
Collections类提供了一个静态的、重载的方法sort,它可以接受列表并按自然顺序排序,或者我们可以使用Comparator提供自定义的排序逻辑。
2.1.按自然/词汇顺序排序
首先,我们要定义输入列表。
private static List<String> INPUT_NAMES = Arrays.asList("john", "mike", "usmon", "ken", "harry");
现在我们先用Collections类来对列表进行自然排序,也就是所谓的lexicographic order。
@Test
void givenListOfStrings_whenUsingCollections_thenListIsSorted() {
Collections.sort(INPUT_NAMES);
assertThat(INPUT_NAMES).isEqualTo(EXPECTED_NATURAL_ORDER);
}
其中,EXPECTED_NATURAL_ORDER是。
private static List<String> EXPECTED_NATURAL_ORDER = Arrays.asList("harry", "john", "ken", "mike", "usmon");
这里需要注意的一些要点是:
- 列表按照其元素的自然排序,以升序进行排序
- 我们可以将任何Collection传递给sort方法,其元素是Comparable(实现Comparable接口)。
- 在这里,我们传递了一个List的String类,它是一个可比较的类,因此,排序工作了。
- Collection类改变了被传递给sort的List对象的状态。因此,我们不需要返回列表
2.2.倒序排序
让我们来看看如何对同一列表按字母顺序进行反向排序。
让我们再次使用sort方法,但现在提供了一个Comparator。
Comparator<String> reverseComparator = (first, second) -> second.compareTo(first);
另外,我们也可以简单地使用Comparator接口中的这个静态方法。
Comparator<String> reverseComparator = Comparator.reverseOrder();
一旦我们有了反向比较器,我们就可以简单地把它传递给sort。
@Test
void givenListOfStrings_whenUsingCollections_thenListIsSortedInReverse() {
Comparator<String> reverseComparator = Comparator.reverseOrder();
Collections.sort(INPUT_NAMES, reverseComparator);
assertThat(INPUT_NAMES).isEqualTo(EXPECTED_REVERSE_ORDER);
}
其中EXPECTED_REVERSE_ORDER为:。
private static List<String> EXPECTED_REVERSE_ORDER = Arrays.asList("usmon", "mike", "ken", "john", "harry");
从中得到的一些相关启示是:
- 因为String类是最终的,所以我们不能扩展和覆盖Comparable接口的compareTo方法来进行反向排序。
- 我们可以使用Comparator接口来实现一个自定义的排序策略,即按照降序字母顺序进行排序。
- 由于Comparator是一个函数式接口,我们可以使用一个lambda表达式
3.使用Comparator接口进行自定义排序
通常情况下,我们必须对一个需要一些自定义排序逻辑的字符串列表进行排序。这时我们要实现Comparator接口,并提供我们想要的排序标准。
3.1.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);
}
这里要注意的是。
- 在Comparator之前的排序结果将是不正确的。[“Godzilla, “Minions” “Sing” , “amazing SpiderMan”]
- String::toLowerCase是一个键提取器,它从一个String类型中提取一个Comparable排序键,并返回一个Comparator<String>
- 用自定义的比较器进行排序后,正确的结果将是。[“amazing SpiderMan”, “Godzilla”, “Minions”, “Sing”]
3.2.比较器对特殊字符进行排序
让我们考虑另一个例子,即有一些名字以特殊字符‘@'开头的列表。
我们希望它们被排序在列表的末尾,其余的应该按自然顺序排序。
@Test
void givenListOfStringsIncludingSomeWithSpecialCharacter_whenCustomComparator_thenListIsSortedWithSpecialCharacterLast() {
List<String> listWithSpecialCharacters = Arrays.asList("@alaska", "blah", "jo", "@ask", "foo");
List<String> sortedNaturalOrder = Arrays.asList("@alaska", "@ask", "blah", "foo", "jo");
List<String> sortedSpecialCharacterLast = Arrays.asList("blah", "foo", "jo", "@alaska", "@ask");
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);
}
最后,一些关键点是:
- 没有Comparator的排序输出是。[“@alaska”, “@ask”, “blah”, “foo”, “jo”]这对我们的情况是不正确的
- 像以前一样,我们有一个键提取器,从一个Comparable类型的String中提取出一个Comparable排序键,并返回一个specialCharacterLastComparator。
- 我们可以通过链式 specialCharacterLastComparator做二次排序,使用thenComparing期待另一个Comparator。
- Comparator.naturalOrder按自然顺序比较可比较的对象。
- 在排序中的Comparator后的最终输出是:[“blah”, “foo”, “jo”, “@alaska”, “@ask”]
4.使用流进行排序
现在,让我们用Java 8 Streams来对String的列表进行排序。
4.1.按自然顺序排序
让我们先按自然顺序排序。
@Test
void givenListOfStrings_whenSortWithStreams_thenListIsSortedInNaturalOrder() {
List<String> sortedList = INPUT_NAMES.stream()
.sorted()
.collect(Collectors.toList());
assertThat(sortedList).isEqualTo(EXPECTED_NATURAL_ORDER);
}
重要的是,这里的sorted方法会返回一个按照自然顺序排序的String流。
另外,这个流的元素是可比较的。
4.2.倒序排序
接下来,让我们传递一个Comparator给sorted,它定义了反向排序的策略。
@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);
}
注意,这里我们使用Lamda函数来定义比较器。
另外,我们也可以得到同样的比较器。
Comparator<String> reverseOrderComparator = Comparator.reverseOrder();
然后我们将简单地把这个reverseOrderComparator 传递给sorted。
List<String> sortedList = INPUT_NAMES.stream()
.sorted(reverseOrder)
.collect(Collectors.toList());
5.使用TreeSet进行排序
TreeSet使用Comparable接口,以排序和升序的方式存储对象。
我们可以简单地将我们的未排序列表转换为TreeSet,然后将其作为List:收集回来。
@Test
void givenNames_whenUsingTreeSet_thenListIsSorted() {
SortedSet<String> sortedSet = new TreeSet<>(INPUT_NAMES);
List<String> sortedList = new ArrayList<>(sortedSet);
assertThat(sortedList).isEqualTo(EXPECTED_NATURAL_ORDER);
}
这种方法的一个约束条件是,我们的原始列表不应该有任何重复的值。
6.使用sort对List进行排序
我们也可以使用List的sort方法对一个列表进行排序。
@Test
void givenListOfStrings_whenSortOnList_thenListIsSorted() {
INPUT_NAMES.sort(Comparator.reverseOrder());
assertThat(INPUT_NAMES).isEqualTo(EXPECTED_REVERSE_ORDER);
}
请注意,sort方法是根据指定的比较器所决定的顺序对这个列表进行排序。
7.对本地语言敏感的列表排序
由于语言规则的不同,按字母顺序排序的结果可能会有差异。
让我们举一个String的List的例子吧。
List<String> accentedStrings = Arrays.asList("único", "árbol", "cosas", "fútbol");
让我们先把它们按正常情况分类。
Collections.sort(accentedStrings);
输出结果将是。[“cosas”, “fútbol”, “árbol”, “único”].
然而,我们希望它们能够使用特定的语言规则进行排序。
让我们创建一个具有特定locale的Collator的实例。
Collator esCollator = Collator.getInstance(new Locale("es"));
注意,在这里我们使用es区域设置创建了一个Collator的实例。
然后,我们可以将这个Collator作为一个Comparator被用于排序的list、Collection,或者使用Streams:
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);
}
8.用重音字对列表进行排序
带有重音或其他装饰的字符在Unicode中可以用几种不同的方式进行编码,因此排序也不同。
我们可以在排序前将重音字符正常化,也可以将重音字符移除。
让我们来看看这两种重音列表的排序方式。
8.1.归一化重音列表和排序
为了准确地对这样的字符串列表进行排序,让我们使用java.text.Normalizer对重音字符进行归一化处理。
让我们从重音字符串的列表开始吧。
List<String> accentedStrings = Arrays.asList("único","árbol", "cosas", "fútbol");
接下来,让我们定义一个Comparator与Normalize函数,并将其传递给我们的sort方法。
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]
重要的是,这里我们使用Normalizer.Form.NFD的形式对数据进行规范化处理,对重音字符使用Canonical分解。还有一些其他形式也可以用于规范化。
8.2.剥离重音和排序
让我们在Comparator中使用StringUtils stripAccents 方法,并将其传递给sort方法。
@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).isEqualTostripAccentSorted);
}
9.使用基于规则的整理器进行排序
我们还可以使用java text.RuleBasedCollator.来定义我们的自定义规则,按字母顺序进行排序。
在这里,让我们定义我们的自定义排序规则,并将其传递给RuleBasedCollator。
@Test void givenListofStrings_whenUsingRuleBasedCollator_then ListIsSortedUsingRuleBasedCollator() 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);
}
需要考虑的一些重要问题是:。
- RuleBasedCollator将字符映射到排序键上。
- 上面定义的规则是<关系>的形式,但在规则中也有其他的形式。
- 字符s小于m,M小于a,其中A小于g,根据规则的定义。
- 如果规则的构建过程失败,将抛出一个格式异常。
- RuleBasedCollator实现了Comparator接口,因此可以被传递给sort。
10.结语
在这篇文章中,我们研究了java中按字母顺序排列列表的各种技术。
首先,我们使用了Collections,然后我们用一些常见的例子解释了Comparator接口。
在Comparator之后,我们研究了sort的List方法,然后是TreeSet。
我们还探讨了如何处理不同地区的String列表的排序,规范化和重音列表的排序,以及使用RuleBasedCollator与自定义规则的使用。
像往常一样,本文的完整源代码可以在GitHub上找到over on GitHub。