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 цього не знає: він просто зберігає «перше» і «друге». А домовленість — уже на вашій совісті.
Приклад ближче до реальності: «значення + пояснення»
Іноді корисно зберігати число й короткий текст поруч — наприклад, «результат обчислення + коментар про те, що це взагалі було».
fun main() {
val result = Pair(200, "OK")
println("code=${result.first}") // code=200
println("message=${result.second}") // message=OK
}
Зверніть увагу: читання result.first мало що підказує про те, що саме там зберігається. Тому зазвичай або дають змінній конкретніше ім’я, або (у наступній лекції) розпаковують значення через деконструкцію. А сьогодні ми чесно тренуємося читати компоненти через first/second.
2. 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, корисно мати під рукою просту шпаргалку.
| Тип | Скільки значень зберігає | Як читати компоненти |
|---|---|---|
|
2 | |
|
3 | |
3. Зручне створення: 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 потрібно застосовувати обережно.
4. Застосунок: «чернетка витрат» у консолі
Щоб не залишати Pair і Triple у світі абстрактних точок і кольорів, почнемо розвивати маленький навчальний консольний застосунок. Нехай це буде супермінімальна «чернетка обліку витрат»: користувач вводить категорію та суму, а ми друкуємо зрозуміле підтвердження.
Сьогодні наше завдання просте: навчитися зберігати один запис витрати як зв’язку «категорія + сума». Для цього Pair підходить ідеально: два значення — один сенс.
Створюємо «витрату» як Pair
fun main() {
val expense = "food" to 250
println("Категорія: ${expense.first}") // Категорія: food
println("Сума: ${expense.second}") // Сума: 250
}
Так, food і 250 можна було зберігати у двох змінних. Але ідея така: іноді зручно передавати це далі як один об’єкт — «ось витрата цілком».
Читаємо дані й збираємо Pair
Ми вже вміємо користуватися readln() і toIntOrNull(). Не будемо ускладнювати: припустімо, користувач вводить категорію в першому рядку, а суму — у другому.
fun main() {
print("Введіть категорію: ")
val category = readln().trim()
print("Введіть суму: ")
val amountText = readln().trim()
val amount = amountText.toInt()
val expense = category to amount
println("Збережено: ${expense.first} -> ${expense.second}") // приклад: Збережено: 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 рівнів.
5. Типові помилки під час роботи з 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’ів».
ПЕРЕЙДІТЬ В ПОВНУ ВЕРСІЮ