ПрограммированиеKotlin-разработчик, Backend-разработчик

Как работает механизм числовых диапазонов (Range и Progression) в Kotlin, как создавать собственные диапазоны и для каких задач их использовать?

Проходите собеседования с ИИ помощником Hintsage

Ответ.

Диапазоны (Range) и прогрессии (Progression) — это встроенный механизм в Kotlin для представления последовательностей значений с определённым шагом. Они часто применяются при работе с циклами, условиями, перебором коллекций и валидацией данных. Range появилась как один из способов сделать синтаксис Kotlin лаконичнее и выразительнее по сравнению с Java.

История вопроса

В Java подобные задачи решались через циклы for и while с индексами — многословно и подвержено ошибкам. В Kotlin появились компактные операторы создания диапазонов (например, 1..10) и методы для задания шагов.

Проблема

  • Необходимость легко передавать диапазон значений (например, все числовые значения от 1 до 100).
  • Повышение удобства при переборе и валидации.
  • Возможность переопределять диапазоны для пользовательских типов.

Решение

Kotlin предоставляет стандартные числовые диапазоны (IntRange, LongRange, CharRange, UIntRange, и т.д.) и интерфейсы для создания собственных прогрессий:

Пример кода:

for (i in 1..5) print("$i ") // 1 2 3 4 5 for (i in 5 downTo 1 step 2) print("$i ") // 5 3 1 // Проверка значения val x = 42 if (x in 1..100) println("В диапазоне!")

Кастомные диапазоны

Можно определить диапазон для своих типов, реализовав операторы rangeTo и Progression:

data class Version(val major: Int, val minor: Int) : Comparable<Version> { override fun compareTo(other: Version) = compareValuesBy(this, other, Version::major, Version::minor) } operator fun Version.rangeTo(other: Version) = VersionRange(this, other) class VersionRange( override val start: Version, override val endInclusive: Version ) : ClosedRange<Version> for (v in Version(1, 0)..Version(1, 2)) println(v)

Ключевые особенности:

  • Компактный синтаксис для создания диапазонов (start..end, downTo, step).
  • Встроенные проверки принадлежности (in, !in).
  • Возможность определять диапазоны и прогрессии для пользовательских типов.

Вопросы с подвохом.

Что возвращает выражение 1..5 на самом деле?

Оно создает экземпляр класса IntRange, реализующего интерфейс ClosedRange<Int>. Это не коллекция, а объект, определяющий границы и шаг. Lazy-реализация.

Почему шаг (step) у Range всегда 1? Как изменить шаг?

По умолчанию step у диапазона равен 1 (или -1 при downTo). Для иного шага используются методы step и downTo. Например:

for (i in 2..10 step 2) println(i)

Можно ли использовать диапазоны с типами, не реализующими Comparable?

Нет, для корректной работы пользовательского диапазона тип обязан реализовывать интерфейс Comparable, иначе оператор rangeTo будет невозможен.

Типовые ошибки и анти-паттерны

  • Попытка изменить шаг у Range без использования метода step.
  • Использование диапазона с типами без Comparable.
  • Перепутанное направление (например, 5..1 ничего не выдаст).

Пример из жизни

Негативный кейс

Разработчик использует цикл for (i in 5..1) без downTo, ожидая, что будет "5, 4, 3, 2, 1", а в итоге цикл ни разу не выполнится.

Плюсы:

  • Простота синтаксиса.

Минусы:

  • Неочевидное поведение отрицательного диапазона.
  • Легко запутаться новичку.

Позитивный кейс

Использование прогрессии с downTo и step для перебора отчётов с нужным интервалом, делая код компактным и самодокументируемым.

Плюсы:

  • Лаконичный стиль.
  • Маловероятны ошибки выхода за границы.

Минусы:

  • Нужно знать особенности шагов и направлений.