Kotlin guide done. (20.10.26)

This commit is contained in:
2026-10-20 12:54:25 +03:00
parent da3ad9dd1a
commit 6853ada348
81 changed files with 30112 additions and 0 deletions
@@ -0,0 +1,273 @@
---
title: kotlinc
description: Командная строка компилятора, REPL, скрипты .main.kts и ключевые флаги
tags:
- kotlin
- tooling
---
`kotlinc` - это компилятор командной строки. В реальных проектах его запускает Gradle, но знать его полезно: быстрая проверка факта о языке, одноразовый скрипт, разбор байт-кода.
```
kotlinc Hello.kt -include-runtime -d hello.jar
java -jar hello.jar
```
```kotlin
fun main() {
println("скомпилировано kotlinc")
}
```
Проверить версию:
```
$ kotlinc -version
info: kotlinc-jvm 2.4.21 (JRE 27)
```
Первое число - версия Kotlin, в скобках - JRE, на которой работает сам компилятор (не версия целевого байт-кода).
## Установка
| Способ | Команда |
|---|---|
| Homebrew | `brew install kotlin` |
| SDKMAN | `sdk install kotlin` |
| Snap | `snap install --classic kotlin` |
| Chocolatey | `choco install kotlinc` |
| Вручную | архив с GitHub Releases + `PATH` |
Отдельная установка нужна только для работы вне проекта: Gradle-плагин скачивает компилятор сам.
## Три режима компиляции
**В каталог с `.class`** - для дальнейшей работы с байт-кодом:
```
kotlinc Demo.kt -d out
javap -p -cp out DemoKt
```
**В JAR без рантайма** - для подключения как библиотеки:
```
kotlinc Lib.kt -d lib.jar
```
JAR не запустится напрямую: в нём нет stdlib.
**В исполняемый JAR** - со встроенной stdlib (~5 МБ):
```
kotlinc App.kt -include-runtime -d app.jar
java -jar app.jar
```
## Ключевые флаги
| Флаг | Что делает |
|---|---|
| `-d <путь>` | каталог или `.jar` для результата |
| `-include-runtime` | вложить stdlib в JAR |
| `-classpath` / `-cp` | зависимости |
| `-jvm-target <v>` | версия байт-кода (по умолчанию **1.8**) |
| `-module-name <n>` | имя модуля (влияет на манглирование `internal`) |
| `-nowarn` | подавить предупреждения |
| `-Werror` | считать предупреждения ошибками |
| `-Wextra` | дополнительные проверки K2 |
| `-progressive` | исправления применяются сразу, без цикла миграции |
| `-language-version <v>` | ограничить возможности языка |
| `-api-version <v>` | запретить API новее указанной версии stdlib |
| `-opt-in <fq.name>` | включить API, требующее opt-in |
| `-verbose` | подробный лог |
| `-X` | список экспериментальных флагов |
**Про `-jvm-target`.** Значение по умолчанию - `1.8`, то есть байт-код major version 52, даже на JDK 27:
```
$ kotlinc Demo.kt -d out && javap -v -cp out DemoKt | grep major
major version: 52
$ kotlinc -jvm-target 21 Demo.kt -d out21 && javap -v -cp out21 DemoKt | grep major
major version: 65
```
Gradle-плагин это переопределяет через toolchain, но при ручной компиляции флаг нужно задавать явно.
**Про `-Werror`.** Останавливает сборку на любом предупреждении:
```
$ kotlinc -Werror Demo.kt -d out
error: warnings found and -Werror specified
Demo.kt:5:13: warning: 'fun old(): Int' is deprecated. устарело.
```
В CI это полезно: не даёт предупреждениям накапливаться. В Gradle - `allWarningsAsErrors.set(true)`.
```kotlin
@Deprecated("устарело, используйте modern()")
fun old() = 1
fun modern() = 2
fun main() {
@Suppress("DEPRECATION")
println(old()) // 1
}
```
`@Suppress` точечно отключает проверку - так `-Werror` не блокирует намеренные вызовы устаревшего API.
## Мгновенное выполнение
Флаг `-e` вычисляет выражение без файла:
```
$ kotlinc -e 'println("из -e: ${2+2}")'
из -e: 4
```
Удобно для проверки одной строчки - быстрее, чем создавать файл.
## Скрипты .main.kts
Файл с расширением `.main.kts` - это Kotlin-скрипт: код на верхнем уровне, без `fun main`.
```
$ cat hello.main.kts
println("из скрипта: ${listOf(1,2,3).sum()}")
$ kotlinc -script hello.main.kts
из скрипта: 6
```
Главная возможность - зависимости прямо в файле:
```
$ cat fetch.main.kts
@file:DependsOn("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.11.0")
import kotlinx.coroutines.*
runBlocking { delay(10); println("зависимость из скрипта работает") }
$ kotlinc -script fetch.main.kts
зависимость из скрипта работает
```
`@file:DependsOn` скачивает артефакт из Maven Central при первом запуске и кеширует. Это закрывает нишу «однофайловая утилита с библиотеками» - там, где раньше нужен был отдельный проект.
Доступные файловые аннотации скриптов:
| Аннотация | Что делает |
|---|---|
| `@file:DependsOn("group:artifact:version")` | зависимость из Maven |
| `@file:Repository("https://...")` | дополнительный репозиторий |
| `@file:CompilerOptions("-jvm-target", "21")` | флаги компилятора |
| `@file:Import("other.main.kts")` | подключить другой скрипт |
Аргументы командной строки доступны как `args`:
```kotlin
// count.main.kts — запуск: kotlinc -script count.main.kts файл.txt
// val path = args.firstOrNull() ?: error("укажите файл")
// println(java.io.File(path).readLines().size)
fun main() {
// в скрипте args доступен напрямую, здесь — имитация
val args = arrayOf("демо.txt")
println("аргументов: ${args.size}") // аргументов: 1
}
```
Shebang делает скрипт исполняемым:
```
#!/usr/bin/env kotlin
println("запущено как ./script.main.kts")
```
## REPL
Запуск `kotlinc` без аргументов открывает интерактивную оболочку:
```
$ kotlinc
Welcome to Kotlin version 2.4.21
>>> val x = 42
>>> x * 2
res1: kotlin.Int = 84
>>> :quit
```
REPL полезен для проверки поведения stdlib. Ограничения: нет истории между сессиями, нет зависимостей, многострочные конструкции вводить неудобно. Для исследований лучше Kotlin Notebook в IDEA или `.main.kts`.
## Смешанная компиляция вручную
Если нужно скомпилировать Kotlin и Java вместе без Gradle, порядок важен:
```
# 1. Kotlin видит Java-исходники
kotlinc -cp . Lib.kt JLib.java -d out
# 2. javac видит скомпилированные Kotlin-классы
javac -cp out -d out Main.java
# 3. запуск с stdlib в classpath
java -cp out:$KOTLIN_HOME/lib/kotlin-stdlib.jar Main
```
Kotlin-компилятор принимает `.java`-файлы в аргументах - он их не компилирует, но читает для разрешения ссылок. Без stdlib в classpath запуск падает:
```
Exception in thread "main" java.lang.NoClassDefFoundError: kotlin/jvm/internal/Intrinsics
```
Подробнее: [Структура проекта](Структура%20проекта.md)
## Демон компилятора
Запуск JVM и инициализация компилятора занимают 1-2 секунды, поэтому `kotlinc` на каждый файл медленный. Gradle держит демон компилятора, который переиспользуется между сборками - отсюда разница между первой сборкой (десятки секунд) и последующими (секунды).
Настройка демона в `gradle.properties`:
```
kotlin.daemon.jvmargs=-Xmx2g
org.gradle.jvmargs=-Xmx4g
kotlin.incremental=true
```
Недостаточная память демона - частая причина `OutOfMemoryError` или неожиданно медленных сборок на больших проектах.
## Полезные комбинации
```
# что реально попало в байт-код
kotlinc Demo.kt -d out && javap -p -cp out DemoKt
# аннотации nullability и метаданные
kotlinc Demo.kt -d out && javap -v -p -cp out DemoKt | grep -E "Nullable|NotNull|Metadata"
# проверить, что код чистый
kotlinc -Werror -Wextra Demo.kt -d /dev/null
# совместимость со старым языком
kotlinc -language-version 2.0 -api-version 2.0 Demo.kt -d out
# список экспериментальных флагов
kotlinc -X
```
Подробнее про чтение байт-кода: [Kotlin в байт-коде](../Взаимодействие%20с%20Java/Kotlin%20в%20байт-коде.md)
> [!info] Отличия от Java
> | Java | Kotlin |
> |---|---|
> | `javac Demo.java` | `kotlinc Demo.kt` |
> | `java Demo` | `java -jar app.jar` или `kotlin DemoKt` |
> | `--release 21` | `-jvm-target 21` |
> | `-Xlint:all -Werror` | `-Wextra -Werror` |
> | JEP 330: `java Demo.java` | `kotlinc -script` или `.main.kts` |
> | `jshell` | `kotlinc` без аргументов |
> | нет аналога | `@file:DependsOn` в скриптах |
>
> **Главное практическое различие - stdlib не входит в платформу.** `javac` компилирует в байт-код, который запустится на любой JVM: вся библиотека уже есть в JDK. Kotlin-код зависит от `kotlin-stdlib.jar`, поэтому нужен либо `-include-runtime`, либо stdlib в classpath. Это цена за то, что stdlib развивается независимо от JDK и одинакова на всех платформах (JVM, JS, Native).
>
> **`-jvm-target 1.8` по умолчанию - решение о совместимости, а не недоработка.** Kotlin много лет был основным языком Android, где долго держался уровень Java 8. Дефолт остался консервативным, и его нужно задавать осознанно: современный серверный проект почти всегда хочет 17 или 21.
>
> **Скрипты в Kotlin сильнее, чем однофайловый запуск в Java.** JEP 330 позволяет `java Demo.java`, но только с тем, что есть в JDK: зависимостей нет. `@file:DependsOn` в `.main.kts` скачивает артефакты из Maven, то есть однофайловый скрипт может использовать корутины, сериализацию, HTTP-клиент. Это полноценная замена Python/Bash для задач автоматизации.
>
> Сравнить: [JVM](../../Программирование%20на%20Java/JVM/JVM.md)
> [!warning] Подводные камни
> **`-jvm-target` по умолчанию 1.8.** Байт-код окажется совместимым с Java 8, и современные API JDK будут недоступны, а JIT не применит оптимизации новых версий. На это легко не обратить внимания - предупреждения нет.
>
> **JAR без `-include-runtime` не запускается:**
> ```
> $ kotlinc App.kt -d app.jar && java -jar app.jar
> Exception in thread "main" java.lang.NoClassDefFoundError: kotlin/jvm/internal/Intrinsics
> ```
> Либо добавьте флаг, либо укажите stdlib в `-cp`.
>
> **`-include-runtime` даёт JAR на ~5 МБ.** Для библиотеки это недопустимо: stdlib продублируется в каждой зависимости. Флаг только для конечных приложений.
>
> **`-e` и `-script` требуют разных форм записи.** `-e` принимает выражение в кавычках, `-script` - путь к `.kts`-файлу. Передать `.kts` в `-e` или выражение в `-script` не получится.
>
> **Файл скрипта должен называться `*.main.kts`, не `*.kts`.** Расширение `.kts` зарезервировано для Gradle-скриптов с другим шаблоном: `@file:DependsOn` в нём не сработает.
>
> **Первый запуск `@file:DependsOn` медленный.** Скачивание артефактов занимает секунды или десятки секунд. Дальше работает кеш `~/.m2`, но в CI без сохранённого кеша задержка повторится на каждом прогоне.
>
> **`-Werror` падает на предупреждениях из зависимостей.** Вызов устаревшего API библиотеки даст warning и остановит сборку. Нужен точечный `@Suppress("DEPRECATION")`, а не отключение флага.
>
> **REPL не видит зависимостей.** `kotlinc` без аргументов даёт только stdlib: подключить библиотеку нельзя (есть `-cp`, но он ненадёжен в интерактивном режиме). Для исследований с библиотеками используйте Kotlin Notebook или `.main.kts`.
>
> **Kotlin-компилятор не компилирует `.java`-файлы.** Передача `.java` в аргументах нужна только для разрешения ссылок; сами классы потом компилирует `javac`. Пропустить второй шаг - получить `ClassNotFoundException` на Java-классе.
>
> **`-module-name` влияет на видимость `internal`.** `internal` компилируется в публичное имя с суффиксом модуля (`helper$main`). Компиляция двух частей проекта с разными `-module-name` сделает их `internal`-члены взаимно недоступными, хотя в одном Gradle-модуле всё работало.
>
> **`-d /dev/null` для проверки работает не везде.** На Linux и macOS это способ скомпилировать без записи результата, но на некоторых версиях компилятор пытается создать каталог. Надёжнее писать во временный каталог и удалять.
Далее: [Gradle и Kotlin DSL](Gradle%20и%20Kotlin%20DSL.md)