1. Навіщо взагалі потрібен import, якщо є package
Коли проєкт маленький, може здаватися, що import — це «якась бюрократія». Але щойно ви додаєте другий пакет і кілька утиліт, починаєте писати ось такі заклинання: app.util.normalizeCommand(...). І ніби все нормально… доки не доводиться робити це разів двадцять в одному файлі. Тоді код починає скидатися на адресну книгу поштового відділення.
Сенс import дуже простий: він дозволяє використовувати коротке імʼя замість повного імені (fully qualified name). Повне імʼя — це «адреса + імʼя», записані через крапки. Наприклад, app.util.normalizeCommand. Якщо у файлі є імпорт import app.util.normalizeCommand, далі ви пишете просто normalizeCommand(...).
Порівняймо на мініприкладі.
Припустімо, у нас є функція в пакеті app.util:
package app.util
fun normalizeCommand(raw: String): String {
return raw.trim().lowercase()
}
Без імпорту в іншому пакеті довелося б викликати так:
package app
fun main() {
val cmd = app.util.normalizeCommand(" HeLP ")
println(cmd) // help
}
З імпортом виходить читабельніше:
package app
import app.util.normalizeCommand
fun main() {
val cmd = normalizeCommand(" HeLP ")
println(cmd) // help
}
Якщо сказати простими словами, import — це: «у цьому файлі я часто користуватимуся ось цим імʼям, тож давайте без зайвих префіксів».
Повне імʼя замість import: коли так навіть краще
Іноді імпорт додавати просто не хочеться. Наприклад, ви використовуєте якусь функцію рівно один раз. Або пишете навчальний приклад і хочете явно показати, звідки взялося імʼя. У таких випадках повне імʼя — цілком нормальний інструмент.
Наприклад, вам знадобився квадратний корінь:
package app
fun main() {
val x = kotlin.math.sqrt(9.0)
println(x) // 3.0
}
Це абсолютно робочий стиль. Ба більше, інколи він навіть корисніший за імпорт: ви бачите «корінь з kotlin.math», а не «якийсь sqrt, незрозуміло звідки».
Практичне правило просте: якщо імʼя використовується рідко — повного імені не соромно. Якщо використовується часто — імпорт рятує очі й клавіатуру.
2. Де живе import і чому він не «під’єднує файл»
Є поширена (і цілком зрозуміла) помилка мислення: «Я зробив import, отже під’єднав файл». У Kotlin імпортується не файл і навіть не «папка», а конкретне імʼя (функція, клас, обʼєкт, властивість), яке оголошено в іншому пакеті.
Ще важливіше: імпорт локальний для файлу. Він діє лише всередині того .kt, де написаний. У сусідньому файлі — усе заново: свій package, свої import.
У Kotlin порядок у файлі зазвичай такий:
- package ... (якщо ви використовуєте пакети)
- import ...
- оголошення: функції, класи тощо
Схематично це виглядає так:
┌──────────────────────────────┐
│ package app │
├──────────────────────────────┤
│ import app.util.normalize... │
│ import kotlin.math.sqrt │
├──────────────────────────────┤
│ fun main() { ... } │
│ fun helper() { ... } │
└──────────────────────────────┘
Якщо ви спробуєте написати import посередині файлу, після fun main(), компілятор буде незадоволений. Kotlin тут строгий, як викладач, який помітив шпаргалку: «імпорти — нагору».
Імпорт як «залежність» файлу
Є корисна звичка: дивитися на список import як на «що цьому файлу потрібно, щоб працювати».
Наприклад:
package app
import app.util.normalizeCommand
import app.format.formatAmount as prettyAmount
import kotlin.math.round as mathRound
Це майже як верхня панель інструментів у майстерні: «ось молоток, ось викрутка, ось ізоляційна стрічка». Якщо імпортів забагато й вони випадкові, файл часто виконує надто багато завдань. Якщо імпортів майже немає, зате всередині файлу величезні конструкції виду a.b.c.d.e.f(...), ви, найімовірніше, страждаєте від браку потрібних імпортів. Або від того, що код саме час рознести по функціях і пакетах.
IDE зазвичай допомагає: вона вміє додавати імпорти автоматично, видаляти невикористані та оптимізувати список.
3. import у навчальному застосунку: виносимо нормалізацію команди
А тепер уявімо, що ми пишемо невеликий консольний застосунок BudgetBuddy. Він ще не використовує колекції (це буде пізніше) і не використовує класи (це теж буде пізніше), але вже вміє читати команди, нормалізувати їх і виконувати прості сценарії.
Зробімо так, щоб main жив у app, а утиліти — в app.util.
Файл src/app/util/Command.kt:
package app.util
fun normalizeCommand(raw: String): String {
return raw.trim().lowercase()
}
Тепер файл src/app/Main.kt:
package app
import app.util.normalizeCommand
fun main() {
print("Введіть команду: ")
val cmd = normalizeCommand(readln())
println("Команда після нормалізації: $cmd")
}
Зверніть увагу на «контракт»: normalizeCommand приймає String і повертає String. Вона не читає readln() сама. Це зручно: функція стає чистою й передбачуваною, а main контролює введення та виведення.
4. import ...*: зручно, але перетворює код на детектив
Зірочка в імпорті (*) означає: «імпортуй усі доступні імена із цього пакета». Це виглядає як чит-код:
import kotlin.math.*
Після цього можна писати sqrt, round, ceil, floor без окремих імпортів.
Проблема в тому, що зірочка робить код менш очевидним: ви відкриваєте файл і бачите round(...). А round — це наш round? Це kotlin.math.round? А може, взагалі щось третє? Чим більший проєкт, тим частіше на це питання доводиться відповідати не «в голові», а пошуком по коду.
Тому в навчальних проєктах і в більшості командних кодстайлів надають перевагу точковим імпортам:
import kotlin.math.sqrt
import kotlin.math.round
import із зірочкою інколи виправданий, але зазвичай — лише в ситуаціях, коли ви точно розумієте контекст. Наприклад, якщо файл цілком присвячений математиці й там справді є з десяток функцій із kotlin.math. У звичайній прикладній логіці зірочка часто перетворюється на «чому воно так називається і звідки воно взялося».
5. Конфлікти імен і alias
Конфлікт імен — це коли в одному файлі у вас зʼявляється два кандидати на одне й те саме коротке імʼя.
Найпоширеніший сценарій: ви написали свою функцію round, а потім захотіли використовувати kotlin.math.round. Або ви написали format, а тоді знайшли в проєкті ще одну format. Kotlin не буде вгадувати «яку ви мали на увазі», бо вгадування — це прямий шлях до багів на кшталт «учора працювало».
Давайте спеціально створимо конфлікт.
Файл src/app/util/Round.kt:
package app.util
fun round(x: Double): Int {
return x.toInt()
}
І тепер у Main.kt ми хочемо і нашу, і математичну:
package app
import app.util.round
import kotlin.math.round
fun main() {
println(round(3.6))
}
Такий код не скомпілюється: два імпорти з однаковим імʼям round створюють неоднозначність.
У Kotlin є три здорові стратегії: користуватися повним імʼям, змінити список імпортів або використовувати alias (імпорт із псевдонімом). Нижче — найчитабельніший і «дорослий» варіант: alias.
Alias‑імпорт: локальне імʼя через as
Alias‑імпорт виглядає так:
import kotlin.math.round as mathRound
Наче ви кажете: «у цьому файлі я буду називати kotlin.math.round імʼям mathRound». Важливо: ви не перейменували функцію в бібліотеці й не змінили проєкт. Ви просто завели локальне прізвисько для імені всередині конкретного файлу.
Перепишемо Main.kt так, щоб він працював і був зрозумілим:
package app
import app.util.round
import kotlin.math.round as mathRound
fun main() {
val x = 3.6
val a = round(x) // наша версія: Int, просто обрізання
val b = mathRound(x) // версія з kotlin.math: Double, нормальне округлення
println("a = $a") // a = 3
println("b = $b") // b = 4.0
}
Тут код читабельний навіть для новачка: видно, що mathRound — це «математичне округлення», а round — щось наше.
Alias при двох formatAmount: приклад ближче до реального застосунку
З round приклад зрозумілий, але трохи штучний. У прикладному коді частіше конфліктують слова на кшталт format, parse, normalize, printReport. Це цілком нормальні імена, які ви хотіли б використовувати в різних місцях.
Зробімо два форматери суми грошей: один «сирий» (просто число), інший «гарний» (з валютою). І спеціально дамо їм однакове імʼя formatAmount, щоб побачити alias у дії.
Файл src/app/util/AmountFormat.kt:
package app.util
fun formatAmount(amount: Int): String {
return amount.toString()
}
Файл src/app/format/AmountFormat.kt:
package app.format
fun formatAmount(amount: Int): String {
return "$amount USD"
}
Тепер у Main.kt ми хочемо обидва: для налагодження — «сирий», для користувача — «гарний».
package app
import app.format.formatAmount as prettyAmount
import app.util.formatAmount as rawAmount
fun main() {
val income = 1200
println("DEBUG: income=${rawAmount(income)}") // DEBUG: income=1200
println("Для користувача: ${prettyAmount(income)}") // Для користувача: 1200 USD
}
Зверніть увагу на дві речі.
Перша: alias‑імпорт допомагає не лише «розрулити конфлікт», а й додати сенсу. prettyAmount читається як «гарний формат», а rawAmount — як «сирий для налагодження».
Друга: alias діє лише у файлі Main.kt. В іншому файлі ви можете вибрати інші аліаси — або взагалі не використовувати alias, якщо конфліктів немає.
6. Міні‑рефакторинг BudgetBuddy: робимо main тоншим
Тепер зберемо невеликий «практичний» шматочок, який виглядає як реальний застосунок (але все ще без колекцій і ООП).
Ідея така: main читає команду, нормалізує її й друкує відповідь. Утиліти введення та нормалізації живуть у app.util. Форматування — у app.format.
Файл src/app/util/Input.kt:
package app.util
fun readTrimmedLine(prompt: String): String {
print(prompt)
return readln().trim()
}
Файл src/app/util/Command.kt:
package app.util
fun normalizeCommand(raw: String): String {
return raw.trim().lowercase()
}
Файл src/app/format/Amount.kt:
package app.format
fun formatAmount(amount: Int): String {
return "$amount USD"
}
Файл src/app/Main.kt:
package app
import app.format.formatAmount
import app.util.normalizeCommand
import app.util.readTrimmedLine
fun main() {
val raw = readTrimmedLine("Введіть команду (income/exit): ")
val cmd = normalizeCommand(raw)
if (cmd == "income") {
println("Ваш дохід: ${formatAmount(1200)}") // Ваш дохід: 1200 USD
} else if (cmd == "exit") {
println("Бувайте!") // Бувайте!
} else {
println("Невідома команда: $raw") // приклад: Невідома команда: qwe
}
}
Що важливо: Main.kt став коротшим і зрозумілішим. Йому не потрібно знати, як саме ми «тримаємо» рядок чи як форматуємо суму. Він просто «під’єднує імена» через import і використовує їх як будівельні блоки.
7. Типові помилки під час роботи з import і alias
Помилка №1: думати, що import діє на весь проєкт.
Імпорт — це налаштування конкретного файлу. Якщо ви додали import app.util.normalizeCommand у Main.kt, це не означає, що в Report.kt воно теж зʼявилося «само». У кожному файлі свій набір імпортів — і це нормально: файли мають бути незалежними й чесно оголошувати, що їм потрібно.
Помилка №2: перетворювати import ...* на стиль «за замовчуванням».
Зірочка економить кілька рядків сьогодні, але часто створює плутанину завтра: стає неясно, звідки взялося імʼя і чому воно саме таке. Особливо болісно це проявляється під час конфліктів імен, коли ви раптом виявляєте, що format є «десь іще», а ви про це не знали. Краще починати з точкових імпортів, а * використовувати усвідомлено й рідко.
Помилка №3: намагатися «перейменувати функцію в проєкті» через alias‑імпорт.
import ... as ... перейменовує імʼя лише всередині одного файлу. Це не рефакторинг і не перейменування оголошення. Якщо ви імпортували kotlin.math.round as mathRound, ви не створили нову функцію й не змінили бібліотеку — ви просто дали локальне прізвисько, щоб код став однозначним.
Помилка №4: «лікувати» конфлікт імен перейменуванням навмання, а не за змістом.
Коли зʼявляється конфлікт, новачок часто робить аліаси виду round1 і round2. Код компілюється, але читати його неможливо. Alias краще добирати так, щоб він пояснював роль: mathRound, prettyAmount, rawAmount, cliNormalize. Це не занудство — це внесок у те, щоб за тиждень ви не зненавиділи себе з минулого.
Помилка №5: залишати сміттєві (невикористовувані) імпорти.
Іноді після експериментів залишаються імпорти, які більше не потрібні. Це не ламає програму, але засмічує файл і приховує реальні залежності коду. Хороша звичка — періодично видаляти невикористовувані імпорти (IDE зазвичай уміє це робити автоматично), особливо якщо ви активно рефакторите й переміщуєте функції між пакетами.
ПЕРЕЙДІТЬ В ПОВНУ ВЕРСІЮ