Сортировка коллекций по нескольким полям в Kotlin: 5 фатальных ошибок

Онлайн-тренажер Python для начинающих

Изучайте Python легко и без перегрузки теорией. Решайте практические задачи с автоматической проверкой, получайте подсказки на русском языке и пишите код прямо в браузере — без необходимости что-либо устанавливать.

Начать курс

Почему «простая» сортировка в Kotlin ломает код?

Привет, коллега! Если ты когда-нибудь писал на Kotlin и пытался отсортировать список задач сначала по дате, а потом по приоритету, то наверняка сталкивался с ситуацией, когда результат выглядит как случайный набор. Ты не один — вопрос «Sort collection by multiple fields in Kotlin» собрал тысячи просмотров на Stack Overflow. Казалось бы, что сложного? Но дьявол, как всегда, в деталях.

В этой статье мы разберём топ ошибок при сортировке по нескольким полям в Kotlin, которые допускают даже опытные разработчики. Ты узнаешь, как работает sortedWith, почему compareBy и thenBy — твои лучшие друзья, и как не потерять данные из-за null. Поехали!

Ошибка №1: Сортировка как в Java — через Comparator вручную

Kotlin — это не Java. Но многие, приходя из мира Java, пишут:

val list = listOf(
    User("Alice", 25),
    User("Bob", 20),
    User("Charlie", 25)
)

val sorted = list.sortedWith(Comparator { a, b ->
    val ageCompare = a.age.compareTo(b.age)
    if (ageCompare != 0) ageCompare else a.name.compareTo(b.name)
})

Да, это работает. Но это ужасно читаемо, и ты легко запутаешься в цепочках if-else. Kotlin предлагает элегантный способ — функции-компараторы.

Как правильно?

val sorted = list.sortedWith(compareBy<User> { it.age }.thenBy { it.name })

Коротко, ясно, без лишней логики. compareBy создаёт первичный компаратор, а thenBy добавляет вторичные поля. Это и есть сортировка по нескольким полям в Kotlin в её идеальном виде.

Ошибка №2: Игнорирование null при сортировке

Допустим, у нас есть список задач, где deadline может быть null. Если ты просто напишешь:

tasks.sortedBy { it.deadline }

…то получишь NullPointerException. Kotlin строг к null, и это прекрасно. Но как быть, если null — это нормально?

Решение: nullsFirst / nullsLast

Используй специальные обёртки:

val sorted = tasks.sortedWith(
    compareBy<Task>(nullsLast()) { it.deadline }
        .thenBy { it.priority }
)

Здесь nullsLast() помещает все null-даты в конец списка. Есть и nullsFirst() — наоборот. Это решает проблему, и твоя сортировка коллекций Kotlin становится безопасной.

Ошибка №3: Путаница между sortedBy и sortedWith

Многие новички пишут:

list.sortedBy { it.age }.sortedBy { it.name }

И удивляются, что сортировка по имени «перебивает» сортировку по возрасту. sortedBy каждый раз создаёт новый список и сортирует только по одному полю. Последний вызов — главный.

Как исправить?

Используй sortedWith с цепочкой compareBy:

list.sortedWith(compareBy({ it.age }, { it.name }))

Или многострочный вариант для сложной логики:

list.sortedWith(compareBy<Person> { it.age }.thenByDescending { it.name })

Запомни: один вызов sortedWith — одна сортировка по нескольким полям.

Ошибка №4: Сортировка в обратном порядке — не там, где надо

Иногда нужно отсортировать по возрастанию возраста, но по убыванию имени. Типичная ошибка:

list.sortedByDescending { it.age }.sortedBy { it.name }

Опять та же проблема — вторая сортировка ломает первую. Правильный подход:

val sorted = list.sortedWith(
    compareBy<Person> { it.age }
        .thenByDescending { it.name }
)

Или для полного контроля:

val sorted = list.sortedWith(
    compareBy(Person::age).thenByDescending(Person::name)
)

Обрати внимание: thenByDescending работает только внутри цепочки compareBy. Всё просто и логично.

Ошибка №5: Сортировка mutable-списков без учёта производительности

Для MutableList есть метод sortWith, который сортирует на месте. Но если ты напишешь:

val mutableList = mutableListOf(...)
mutableList.sortWith(compareBy { it.field1 }.thenBy { it.field2 })

…то всё будет ок. Но ошибка в том, что некоторые используют sortedWith на mutable-списке, создавая новый объект, а потом удивляются, почему исходный не изменился.

Совет

  • Если нужен новый список — используй sortedWith (возвращает List).
  • Если нужно изменить текущий — используй sortWith (работает с MutableList).

Это сэкономит память и время. Сортировка коллекций Kotlin должна быть осознанной.

Продвинутый приём: кастомный компаратор для сложных случаев

Бывает, что стандартных функций недостаточно. Например, нужно отсортировать по длине строки, а потом по алфавиту. Тут пригодится кастомный Comparator:

val customComparator = Comparator { a, b ->
    val byLength = a.name.length.compareTo(b.name.length)
    if (byLength != 0) byLength else a.name.compareTo(b.name)
}
val sorted = list.sortedWith(customComparator)

Но не злоупотребляй — для 90% случаев хватает compareBy и thenBy.

Как не допустить ошибок? Чек-лист

Вот краткий список того, что нужно помнить при сортировке по нескольким полям в Kotlin:

  • Всегда используй sortedWith + compareBy для множественной сортировки.
  • Не вызывай sortedBy несколько раз подряд — последняя сортировка отменяет предыдущие.
  • Обрабатывай null с помощью nullsFirst() или nullsLast().
  • Для обратного порядка используй thenByDescending внутри цепочки.
  • Выбирай sortedWith (новый список) или sortWith (изменение исходного) в зависимости от задачи.

Вывод: сортируй с ум

Блоги

Рекомендаций по книгам