Перейти к основному содержимому

Сортировка списка по алфавиту в Java

· 9 мин. чтения

1. Введение

В этом руководстве мы рассмотрим различные способы сортировки списка по алфавиту в Java.

Сначала мы начнем с класса Collections , а затем воспользуемся интерфейсом Comparator . Мы также будем использовать API списка для сортировки в алфавитном порядке, за которым следуют потоки, и, наконец, использовать TreeSet.

Кроме того, мы расширим наши примеры, чтобы изучить несколько различных сценариев, включая сортировку списков на основе определенного языкового стандарта, сортировку списков с диакритическими знаками и использование RuleBasedCollator для определения наших пользовательских правил сортировки.

2. Сортировка с использованием класса коллекций

Во-первых, давайте посмотрим, как мы можем отсортировать список с помощью класса 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");

Вот несколько важных моментов, на которые следует обратить внимание:

  • Список отсортирован в порядке возрастания в соответствии с естественным порядком его элементов.
  • Мы можем передать любую коллекцию для сортировки , элементы которой сопоставимы (реализовать интерфейс Comparable )
  • Здесь мы передаем класс List of String, который является классом Comparable, и, следовательно, сортировка работает .
  • Класс Collection изменяет состояние объекта List , передаваемого в sort . Поэтому нам не нужно возвращать список

2.2. Сортировка в обратном порядке

Давайте посмотрим, как мы можем отсортировать тот же список в обратном алфавитном порядке.

Давайте снова воспользуемся методом сортировки , но теперь предоставим 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 является окончательным, мы не можем расширить и переопределить метод compareTo интерфейса Comparable для обратной сортировки .
  • мы можем использовать интерфейс Comparator для реализации индивидуальной стратегии сортировки, которая сортирует в алфавитном порядке по убыванию .
  • поскольку Comparator — это функциональный интерфейс, мы можем использовать лямбда-выражение

3. Пользовательская сортировка с использованием интерфейса компаратора

Часто нам приходится сортировать список строк, для которых требуется некоторая пользовательская логика сортировки. Именно тогда мы реализуем интерфейс Comparator и предоставляем желаемые критерии сортировки.

3.1. Компаратор для сортировки списка со строками верхнего и нижнего регистра

Типичным сценарием, который может потребовать пользовательской сортировки, может быть смешанный список строк, начиная с верхнего и нижнего регистра.

Давайте настроим и протестируем этот сценарий:

@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);
}

Обратите внимание:

  • результат сортировки перед компаратором будет неверным: ["Годзилла", "Миньоны", "Поют", "удивительный Человек-Паук"]
  • String::toLowerCase — это экстрактор ключей, который извлекает ключ сортировки Comparable из типа String и возвращает Comparator<String> .
  • правильный результат после сортировки с помощью специального компаратора будет следующим: ["Удивительный Человек-Паук", "Годзилла", "Миньоны", "Пой"]

3.2. Компаратор для сортировки специальных символов

Давайте рассмотрим еще один пример списка с некоторыми именами, начинающимися со специального символа '@'.

Мы хотим, чтобы они сортировались в конце списка, а остальные сортировались в естественном порядке:

@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);
}

Наконец, некоторые ключевые моменты:

  • Вывод сортировки без компаратора : ["@alaska", "@ask", "blah", "foo", "jo"] , что неверно для нашего сценария .
  • Как и раньше, у нас есть экстрактор ключей, который извлекает ключ сортировки Comparable из типа String и возвращает специальныйCharacterLastComparator.
  • Мы можем связать специальныйCharacterLastComparator, чтобы выполнить вторичную сортировку, используя thenComparing , ожидая другого компаратора .
  • Comparator.naturalOrder сравнивает сопоставимые объекты в естественном порядке.
  • окончательный вывод после компаратора в сортировке: ["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, отсортированный в соответствии с естественным порядком.

Кроме того, элементы этого потока являются 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<String> reverseOrderComparator = Comparator.reverseOrder();

Затем мы просто передадим этот reverseOrderComparator в sorted :

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

5. Сортировка с помощью TreeSet

TreeSet хранит объекты в отсортированном и возрастающем порядке, используя интерфейс Comparable .

Мы можем просто преобразовать наш несортированный список в 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);
}

Ограничение этого подхода состоит в том, что в исходном списке не должно быть повторяющихся значений, которые необходимо сохранить в отсортированном списке .

6. Сортировка с использованием сортировки по списку

Мы также можем отсортировать список, используя метод sort списка :

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

Обратите внимание, что метод sort сортирует этот список в соответствии с порядком, определяемым указанным Comparator .

7. Сортировка списка с учетом локали

В зависимости от региона результат сортировки по алфавиту может отличаться из-за языковых правил .

Давайте возьмем пример List of String :

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

Давайте сначала отсортируем их как обычно:

Collections.sort(accentedStrings);

Результатом будет: [ «cosas», «fútbol», «árbol», «unico» ].

Однако мы хотим, чтобы они сортировались с использованием определенных языковых правил.

Давайте создадим экземпляр Collator с определенной локалью :

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

Обратите внимание, здесь мы создали экземпляр Collator, используя локаль es .

Затем мы можем передать этот Collator в качестве Comparator , используемого для сортировки в list , Collection или с использованием Streams:

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

Результатом сортировки теперь будет: [ arbol, 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 и передадим его нашему методу сортировки :

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

Отсортированный список после нормализации будет следующим: [ arbol, cosas, fútbol, único ]

Важно отметить, что здесь мы нормализуем данные, используя форму Normalizer.Form.NFD , использующую каноническую декомпозицию для символов с диакритическими знаками. Есть несколько других форм, которые также можно использовать для нормализации.

8.2. Убрать акценты и отсортировать

Давайте воспользуемся методом StringUtils stripAccents в Comparator и передадим его в метод 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 мы рассмотрели метод сортировки List , за которым следует TreeSet .

Мы также изучили, как мы можем справиться с сортировкой списка String в другом языковом стандарте, нормализацией и сортировкой акцентированных списков, а также с использованием RuleBasedCollator с пользовательскими правилами .

Как всегда, полный исходный код этой статьи можно найти на GitHub .