1. Зачем вообще нужен import, если есть package
Когда проект маленький, кажется, что import — это «какая-то бюрократия». Но как только вы заводите второй пакет и пару утилит, вы начинаете писать вот такие заклинания: app.util.normalizeCommand(...). И вроде бы нормально… пока вы не делаете это 20 раз в одном файле. Тогда код начинает выглядеть как адресная книга почтового отделения.
Смысл 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 обычно умеет это делать автоматически), особенно если вы активно рефакторите и двигаете функции по пакетам.
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ