在 Java 中按字母順序對列表進行排序
一、簡介
在本教程中,我們將探索在 Java 中按字母順序對列表進行排序的各種方法。
首先,我們將從Collections
類開始,然後使用Comparator
接口。我們還將使用List's
API 按字母順序排序,然後是streams
,最後使用TreeSet.
此外,我們將擴展我們的示例以探索幾種不同的場景,包括基於特定語言環境對列表進行排序、對重音列表進行排序以及使用[RuleBasedCollator](https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/text/RuleBasedCollator.html)
定義我們的自定義排序規則。
2. 使用Collections
類排序
首先,讓我們看看如何使用Collections
類對列表進行排序。
Collections
類提供了一個靜態的重載方法sort
,它可以獲取列表並按自然順序對其進行排序,或者我們可以使用Comparator
提供自定義排序邏輯。
2.1。按自然/字典順序排序
首先,我們將定義輸入列表:
private static List<String> INPUT_NAMES = Arrays.asList("john", "mike", "usmon", "ken", "harry");
現在我們將首先使用Collections
類按自然順序對列表進行排序,也稱為字典順序:
@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
傳遞給其元素是Comparable
的sort
方法(實現Comparable
接口) - 在這裡,我們傳遞了一個 String 類的
List
,String class which is a Comparable
類,因此可以進行排序 - 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
類是 final 的,我們不能擴展和覆蓋Comparable
接口的compareTo
方法進行反向排序 - 我們可以使用
Comparator
接口來實現自定義排序策略,即按字母descending
排序 - 由於
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>
的鍵提取器 - 使用自定義
Comparator
器排序後的正確結果將是:[“amazing SpiderMan”, “Godzilla”, “Minions”, “Sing”]
3.2. Comparator
器對特殊字符進行排序
讓我們考慮另一個以特殊字符“@”開頭的名稱的列表示例。
我們希望它們在列表的末尾排序,其餘的應該按自然順序排序:
@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);
}
最後,一些關鍵點是:
- 沒有
Comparator
的排序的輸出是:[“@alaska”, “@ask”, “blah”, “foo”, “jo”]
這對於我們的場景是不正確的 - 像以前一樣,我們有一個鍵提取器,它從
String
類型中提取一個Comparable
排序鍵並返回一個specialCharacterLastComparator
- 我們可以使用 thenComparing 鏈接
specialCharacterLastComparator
進行二次排序,thenComparing expecting another Comparator
-
Comparator.naturalOrder
按自然順序比較Comparable
對象。 - 排序中
Comparator
器之後的最終輸出是:[“blah”, “foo”, “jo”, “@alaska”, “@ask”]
4. 使用Streams
排序
現在,讓我們使用 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 流。
此外,此 Stream 的元素是Comparable
。
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
或者,我們可以得到相同的Comparator
:
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. 使用List
上的sort
進行排序
我們還可以使用 List 的sort
方法對List
進行排序:
@Test
void givenListOfStrings_whenSortOnList_thenListIsSorted() {
INPUT_NAMES.sort(Comparator.reverseOrder());
assertThat(INPUT_NAMES).isEqualTo(EXPECTED_REVERSE_ORDER);
}
請注意, sort
方法根據指定的 Comparator 指定的順序對該列表進行排序。
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
locale 創建了一個 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");
接下來,讓我們定義一個帶有Normalize
函數的Comparator
並將其傳遞給我們的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
對重音字符使用規範分解來規範化數據。還有一些其他形式也可用於規範化。
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. 使用Rule-Based Collator
整理器進行排序
我們還可以使用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
之後,我們研究了List
和TreeSet
的sort
方法。
我們還探討瞭如何處理在不同語言環境中對String
列表進行排序、對重音列表進行規範化和排序,以及將RuleBasedCollator
與自定義規則一起使用
與往常一樣,可以在 GitHub 上找到本文的完整源代碼。