JavaRush /Курсы /Kotlin SELF /Кортежи: Pair и

Кортежи: Pair и Triple

Kotlin SELF
10 уровень , 1 лекция
Открыта

1. Как вернуть несколько значений из функции?

Если вы только начинаете программировать, очень легко подумать: «Ну если мне надо вернуть два значения — я просто… верну два значения». Увы, большинство языков (и Kotlin в том числе) обычно требуют, чтобы функция возвращала ровно одно значение.

На этот случай у программистов есть лайфхак: вернуть не «два значения», а один контейнер, внутри которого лежат два значения. И вот тут в Kotlin как раз появляются Pair и Triple.

Представьте себе коробку с наклейкой: «Внутри две вещи». Это и есть Pair. А коробка «Внутри три вещи» — это Triple. Kotlin даёт такие коробки «из коробки» (простите за каламбур), чтобы вы не изобретали велосипед и не склеивали данные в строку вида "10,20".

Pair: контейнер из двух значений

Pair — это стандартный тип Kotlin, который хранит ровно два связанных значения. Именно связанных: то есть таких, которые логически идут вместе. Не «котлеты и космические ракеты», а что-то вроде «имя и возраст», «координата X и координата Y», «код ответа и текст сообщения».

Важно, что Pair не навязывает смысл компонентам. У него нет полей x и y, нет name и age. Есть только честные и немного скучные first и second. Смысл появляется из контекста и имён переменных вокруг.

Создаём Pair через Pair(a, b) и читаем через first/second

Самый прямой способ: вызвать конструктор Pair(...).

fun main() {
    val point = Pair(10, 20)

    println(point.first)   // 10
    println(point.second)  // 20
}

Здесь point — это «точка», а значит мы договорились, что first — это x, а second — это y. Kotlin этого не знает. Kotlin просто хранит «первое» и «второе». Договорённость — на ваше совести.

Пример ближе к реальности: «значение + пояснение»

Иногда полезно хранить число и короткий текст рядом — например, «результат вычисления + комментарий, что это вообще было».

fun main() {
    val result = Pair(200, "OK")

    println("code=${result.first}")     // code=200
    println("message=${result.second}") // message=OK
}

Обратите внимание: читать result.first мало что говорит о том, что там храниться. Поэтому мы обычно либо даём переменной более конкретное имя, либо (в следующей лекции) распакуем это через деконструкцию. Но сегодня мы честно тренируемся читать значения через first/second.

3. Triple: контейнер из трёх значений

Triple — это брат-близнец Pair, только у него внутри три значения. Это удобно, когда данных действительно три и они образуют один смысловой набор. Классический пример — RGB: красный, зелёный, синий.

Как и у Pair, имена компонент скучные, но очевидные: first, second, third. Kotlin как будто говорит: «Я только храню. Смысл — ваша работа».

Создаём Triple и читаем через first/second/third

fun main() {
    val rgb = Triple(255, 128, 0)

    println("r=${rgb.first}")   // r=255
    println("g=${rgb.second}")  // g=128
    println("b=${rgb.third}")   // b=0
}

Шпаргалка: как читать компоненты

Когда глаза устают от first/second/third, полезно держать простую шпаргалку.

Тип Сколько значений хранит Как читать компоненты
Pair
2
first, second
Triple
3
first, second, third

4. Удобное создание: to и вывод типов

Сейчас будет маленький кусочек «магии Kotlin», но не бойтесь: магия здесь доброкачественная.

Кроме Pair(a, b) есть супер-популярная запись:

val range = 1 to 10

Она создаёт Pair(1, 10).

Эта форма работает потому, что to — это функция, которую Kotlin разрешает вызывать в infix (инфиксной) форме: когда имя функции стоит между двумя значениями. Kotlin описывает правила infix-вызовов (у функции должен быть один параметр и ещё несколько требований) в отдельной документации про infix-нотацию.

a to b — это пара, а не «специальный синтаксис»

Важно понимать: to не «часть языка для пар», а обычная функция (просто ей удобно пользоваться в инфиксной форме). Поэтому a to b — это именно создание Pair(a, b).

Важно: 1 to 10 — не диапазон 1..10

Очень важно: 1 to 10 — это не диапазон 1..10. Это просто пара значений, где first = 1, second = 10.

fun main() {
    val fromTo = 1 to 10

    println("from=${fromTo.first}") // from=1
    println("to=${fromTo.second}")  // to=10
}

Делайте имена переменных «говорящими»

Читаемость Pair почти всегда держится на именах переменных. Если назвать переменную удачно, код становится заметно дружелюбнее.

fun main() {
    val minMax = 3 to 12

    println("min=${minMax.first}") // min=3
    println("max=${minMax.second}") // max=12
}

Запись a to b настолько распространена, что Kotlin прямо использует её в примерах документации, когда нужно «объеденить пары».

Какие типы хранятся внутри Pair/Triple

Сейчас будет важный момент: Pair и Triple умеют хранить что угодно. Хотите Int и String — пожалуйста. Хотите String и Boolean — тоже можно. Kotlin при этом часто сам выводит типы, как мы уже привыкли на переменных.

То есть, если вы пишете val p = 10 to 20, Kotlin понимает, что это Pair<Int, Int>. Но мы сегодня не углубляемся детали — это будет сильно позже. Пока достаточно понимать практику: типы внутри пары соответствуют тому, что вы туда положили.

Pair из разных типов

fun main() {
    val user = Pair("Ada", 36)

    println(user.first)   // Ada
    println(user.second)  // 36
}

Тут first — строка, second — число. Kotlin это прекрасно переваривает.

Triple — только если это один смысл, а не «мешок всего»

Только помните: Triple — не мешок «что бы ещё запихнуть». Если три значения не образуют единую идею, потом вы сами будете смотреть на это как на загадку.

fun main() {
    val movie = Triple("Matrix", 1999, true)

    println(movie.first)  // Matrix
    println(movie.second) // 1999
    println(movie.third)  // true
}

Работает? Да. Читается? С натяжкой. Потому что true — это “что”? «Смотрел»? «Понравилось»? «Фильм существует»? Вот поэтому Pair/Triple нужно применять аккуратно.

5. Приложение: «черновик расходов» в консоли

Чтобы не оставить Pair и Triple в мире абстрактных точек и цветов, начнём развивать маленькое учебное консольное приложение. Пусть это будет супер-минимальный «черновик учёта расходов»: пользователь вводит категорию и сумму, а мы печатаем понятное подтверждение.

Сегодня наша задача простая: научиться хранить одну запись расхода как связку «категория + сумма». Для этого Pair подходит идеально: два значения, один смысл.

Создаём «расход» как Pair

fun main() {
    val expense = "food" to 250

    println("Category: ${expense.first}") // Category: food
    println("Amount: ${expense.second}")  // Amount: 250
}

Да, food и 250 можно было хранить в двух переменных. Но идея такая: иногда нам удобно передавать это дальше как один объект: «вот расход целиком».

Читаем данные и собираем Pair

Мы уже умеем readln() и toIntOrNull(). Не будем усложнять: допустим, пользователь вводит категорию в первой строке, сумму — во второй.

fun main() {
    print("Enter category: ")
    val category = readln().trim()

    print("Enter amount: ")
    val amountText = readln().trim()
    val amount = amountText.toInt()

    val expense = category to amount
    println("Saved: ${expense.first} -> ${expense.second}") // пример: Saved: food -> 250
}

Это ещё не «настоящий» учёт расходов, но как учебный шаг — отлично: мы увидели, как Pair становится маленькой «упаковкой» для связанной информации.

Когда Pair/Triple уместны, а когда лучше остановиться

Pair и Triple — как скотч. В хозяйстве полезно, но если вы построили космический корабль из одного скотча, то, возможно, вы что-то упустили в проектировании.

Обычно Pair/Triple уместны, когда значения действительно маленькие, их мало, и вы быстро понимаете по контексту, что означает first и second. Например, x to y для координаты, min to max для границ, code to message для результата.

Но если вы ловите себя на том, что постоянно думаете «а first — это что?» и начинаете писать комментарии типа // first = category, second = amount, это сигнал: возможно, вам нужен более «именованный» результат. Но как правильно это делать я расскажу вам уровней через 10.

7. Типичные ошибки при работе с Pair и Triple

Ошибка №1: путаница порядка компонентов (first/second).
Самая популярная проблема — перепутать, что лежит в first, а что в second. Особенно когда в разных местах кода вы создаёте похожие пары, но «по-разному договорились»: где-то (min, max), а где-то (max, min). Это не ошибка компилятора — это ошибка смысла. Спасает дисциплина: фиксируйте порядок и держите его везде одинаковым, а переменным давайте имена, которые этот порядок подсказывают (minMax, fromTo, xToY).

Ошибка №2: использовать Pair для несвязанных данных «потому что можно».
Технически вы можете положить в Pair что угодно. Но если два значения не образуют один смысл, чтение превращается в ребус. Через неделю вы откроете код и будете угадывать, почему val x = pair.first, а x — это вообще что? Pair — это не «пакет на вынос» для случайных остатков.

Ошибка №3: надеяться, что first означает «главное».
Иногда новичкам кажется, что first — это «главное поле», а second — «дополнительное». Kotlin так не считает. Там нет главного и второстепенного, есть только порядок. Если смысл требует «главности», это нужно выражать именами переменных и логикой кода, а не надеяться на магию слова first.

Ошибка №4: Triple как «мешок всего».
Triple легко превращается в мини-хаос: «и имя туда, и возраст, и флаг, и… ой, а четвёртого места нет». Если вы уже чувствуете, что Triple не помещает реальный смысл, это не «плохой Triple», это хороший сигнал, что модель данных просит более нормальную форму. Пока просто запомните этот сигнал — мы к нему вернёмся в будущем, когда у нас появятся собственные типы.

Ошибка №5: длинные цепочки .first.first....
Когда вы начинаете видеть конструкции вроде something.first.second, значит где-то вы склеили Pair внутри Pair и уже сами себе сделали лабиринт. В такой ситуации лучше остановиться и разложить промежуточные значения в переменные с нормальными именами. Даже если это на две строки длиннее — мозгу будет дешевле, чем разгадывать «матрёшку из first’ов».

1
Задача
Kotlin SELF, 10 уровень, 1 лекция
Недоступна
Координаты на карте
Координаты на карте
1
Задача
Kotlin SELF, 10 уровень, 1 лекция
Недоступна
Границы маршрута
Границы маршрута
1
Задача
Kotlin SELF, 10 уровень, 1 лекция
Недоступна
Делёж добычи
Делёж добычи
1
Задача
Kotlin SELF, 10 уровень, 1 лекция
Недоступна
Паспорт прямоугольника
Паспорт прямоугольника
Комментарии (1)
ЧТОБЫ ПОСМОТРЕТЬ ВСЕ КОММЕНТАРИИ ИЛИ ОСТАВИТЬ КОММЕНТАРИЙ,
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ
Khagnar Уровень 62
13 марта 2026
потом узнаешь, что существует record классы и забываешь о pair c triple