JavaRush /Курсы /Kotlin SELF /Kotlin на JVM: как вызывать Java API

Kotlin на JVM: как вызывать Java API

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

1. Kotlin/JVM и Java standard library: что вообще происходит

Когда вы пишете Kotlin‑код «обычного» консольного приложения, вы почти наверняка пишете Kotlin/JVM: ваш код компилируется в байткод и выполняется на JVM (Java Virtual Machine). Это значит, что рядом с вашим кодом всегда находится JDK (Java Development Kit) со стандартными пакетами java.*: строки, коллекции, системные функции, UUID и много чего ещё. По сути, Kotlin не «заменяет» Java‑мир — он очень прагматично живёт вместе с ним и спокойно берёт готовые инструменты из JDK.

Чтобы не пугаться масштаба, запомним простую мысль: Java API для Kotlin — это просто «ещё одна библиотека», только очень большая и уже установленная почти везде, где есть JVM.

Java API в Kotlin выглядит «обычно»: точка, скобки и без ритуалов

Если вы ожидаете, что для вызова Java‑кода нужно открыть портал в ад, произнести заклинание и принести в жертву пару скобочек — вы приятно разочаруетесь. В Kotlin вызов Java API выглядит максимально буднично: создаём объект через «вызов типа» и вызываем методы через точку.

Здесь полезно выстроить интуицию: Java‑класс — это просто тип, который можно импортировать, создать через конструктор и использовать, как любой другой объект. Java‑метод — это просто метод: obj.method(...). А Java «статический метод» — это просто вызов через имя класса: Type.staticMethod(...). Да, это настолько скучно — и в этом весь кайф.

2. Пакеты и import: как Kotlin находит Java‑классы

Когда проект маленький, хочется писать «коротко и без лишних слов». Но компилятор не телепат: если вы используете класс UUID, ему нужно понимать, какой именно UUID (из какого пакета) вы имеете в виду. Поэтому у нас два основных способа обратиться к Java‑типу: через import или через полное имя.

Вспоминаем идею адреса: пакет — это как «улица», класс — это «дом». Если вы не написали import, то можете «дойти пешком» по полному адресу: java.util.UUID. А если написали import, то дальше можно говорить коротко: UUID. Это тот же принцип, который вы уже использовали с собственными функциями и пакетами, просто теперь пакеты начинаются на java..

Способ №1: полное имя

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

fun main() {
    val id = java.util.UUID.randomUUID().toString()
    println(id) // например: 6c6f3a7a-0a51-4b4e-9a61-1dbd7c2f0fb2
}

Здесь java.util.UUID — Java‑класс из JDK. Метод randomUUID() — «статический» (вызов через имя класса), а toString() превращает UUID в строку.

Способ №2: import

import делает код чище и короче. Особенно когда класс используется много раз. В нормальных проектах это основной путь: вы импортируете нужные типы и спокойно работаете с короткими именами.

import java.util.UUID

fun main() {
    val id = UUID.randomUUID().toString()
    println(id) // например: 1c0d6e2a-9d0d-4e1a-9e84-63aaf1c30a24
}

Если IntelliJ IDEA подсвечивает красным UUID, обычно это значит одно из двух: либо забыли import, либо опечатались в имени.

3. Конструкторы и static‑методы Java: базовая практика

Когда вы видите Java‑класс, не нужно думать «это другое». В Kotlin создание объекта через конструктор выглядит как вызов функции: Type(...). Это относится и к Kotlin‑классам (позже в курсе), и к Java‑классам (уже сейчас). Хорошая новость: синтаксис один и тот же.

Важно только помнить, что многие Java‑объекты по стилю мутабельны (изменяемы): вы создаёте объект, а потом «дописываете» в него данные методами add, append, put и так далее. В Kotlin мы тоже так умеем (например, MutableList), просто сегодня это особенно заметно.

Пример: StringBuilder() как Java‑класс

StringBuilder вы могли уже видеть как инструмент для сборки длинных строк. На JVM это класс из Java мира, и Kotlin с ним дружит без вопросов.

fun main() {
    val sb = StringBuilder()
    sb.append("Kotlin ")
    sb.append("и Java дружат.")
    println(sb.toString()) // Kotlin и Java дружат.
}

Обратите внимание на стиль: append меняет объект sb «на месте». Это нормально и ожидаемо для Java API.

Java static‑методы: вызов через имя типа

В Java есть концепция static: метод принадлежит не конкретному объекту, а самому классу. Kotlin в общем случае не заставляет вас думать про static как про отдельную сущность: вы просто вызываете метод через имя типа — и всё.

Почему это важно? Потому что многие «утилиты» в JDK сделаны именно так: System.getenv(...), System.getProperty(...), UUID.randomUUID() и т.п. Это очень частый паттерн в реальных приложениях: «дай системную информацию», «сгенерируй идентификатор», «возьми текущие настройки».

Пример: System.getProperty(...) — узнаём что-нибудь о среде

System — это Java‑класс, который помогает общаться с окружающим миром (операционная система, свойства JVM, окружение). Для CLI‑программы это полезно хотя бы для дружелюбного «привет, пользователь».

fun main() {
    val user = System.getProperty("user.name")
    println("Привет, $user!") // Привет, alex!
}

Свойств очень много. Два часто используемых: "user.name" и "user.dir" (рабочая директория процесса). Про файлы мы сегодня не говорим, но директория как строка уже может быть полезной для диагностики.

Мини‑таблица: «статический» vs «обычный» метод

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

Что делаем Как выглядит Пример
Вызываем метод через класс
Type.method()
UUID.randomUUID()
Создаём объект
Type(...)
StringBuilder()
Вызываем метод через объект
obj.method()
sb.append("...")

Эта схема простая, но она закрывает 80% «почему это не компилируется?!» на старте.

4. Java‑коллекции: ArrayList и «мутация — это норма»

К этому месту вы уже уверенно пользуетесь коллекциями Kotlin (List, MutableList, Map), и это отлично. Но на JVM рядом существует огромный пласт кода и библиотек, которые используют Java‑коллекции: ArrayList, HashMap, HashSet и т.д. Kotlin умеет с ними работать напрямую. Более того, на JVM это настолько привычно, что иногда вы даже не заметите, где заканчивается Kotlin и начинается Java.

Ключевая практическая разница для новичка: Java‑коллекции по дизайну в основном изменяемые, и это считается нормой. Поэтому метод add будет менять список, а не возвращать новый.

Пример: создаём ArrayList и добавляем элементы

import java.util.ArrayList

fun main() {
    val xs = ArrayList<Int>()
    xs.add(10)
    xs.add(20)
    println(xs.size) // 2
}

Сравните это с Kotlin‑стилем listOf(10, 20): там список read‑only (по ссылке), а здесь — «живой» и меняющийся.

Зачем вообще трогать Java‑коллекции, если есть Kotlin‑коллекции?

В этой лекции мы не будем углубляться в тонкости совместимости (это отдельная большая тема). Но практическая причина простая: огромное количество API в мире JVM исторически принимает и возвращает Java‑коллекции. И если вы хотите уверенно пользоваться библиотеками, вам нужно хотя бы не пугаться ArrayList.

5. Мини‑проект: «Text Analyzer» становится взрослее

До этого дня мы писали довольно автономные программы: ввёл текст → посчитал → вывел отчёт. Сегодня мы сделаем маленький, но очень «реальный» шаг: добавим в отчёт служебную информацию, которую обычно хочется видеть в логах и отчётах. Например: идентификатор запуска, имя пользователя и рабочую директорию. Всё это даст нам Java API из JDK — бесплатно, без зависимостей и без магии.

Чтобы примеры связались в единое целое, будем считать, что у нас уже есть упрощённый анализатор текста (в стиле лекций про пайплайны): токенизация, подсчёт частот, вывод top‑N.

Генерируем идентификатор запуска через UUID.randomUUID()

В длинных сценариях полезно уметь сказать: «Вот этот отчёт относится к конкретному запуску программы». Особенно если пользователь присылает вам скриншот или кусок лога. UUID — это просто уникальная строка, которую легко сгенерировать и почти невозможно случайно повторить.

Сделаем функцию newRunId(), которая возвращает строку. Внутри используем Java‑класс UUID и его static‑метод.

import java.util.UUID

fun newRunId(): String {
    return UUID.randomUUID().toString()
}

fun main() {
    println(newRunId()) // например: 9a9dbb6c-2c92-40bb-9c5c-2ee7d4c8d77b
}

Мы пока не обсуждаем «почему так устроено» — нам важно другое: вы увидели, что Java static‑метод в Kotlin вызывается абсолютно естественно.

Берём информацию о среде через System.getProperty(...)

Когда программа запускается на разных машинах, иногда хочется понять: «Где я вообще работаю?». Это особенно актуально, когда у пользователя «у меня не работает», а у вас «у меня работает». Простые системные свойства помогают хотя бы начать разговор предметно: какой пользователь, какая директория запуска.

Сделаем небольшую функцию, которая собирает пару значений.

fun runtimeInfo(): String {
    val user = System.getProperty("user.name")
    val dir = System.getProperty("user.dir")
    return "user=$user, dir=$dir"
}

fun main() {
    println(runtimeInfo()) // user=alex, dir=/Users/alex/projects/analyzer
}

Здесь всё ещё нет ничего «Kotlin‑специфичного»: это обычные вызовы Java API и обычные строки Kotlin.

Собираем заголовок отчёта через StringBuilder

Когда вы выводите отчёт в консоль, очень быстро хочется красиво оформить многострочный текст: заголовок, метаданные, затем результат анализа. Конкатенация строк через + работает, но для большого текста проще и понятнее собрать всё в StringBuilder и вернуть готовую строку. Это классический подход на JVM (и да, это Java‑класс).

Сделаем функцию buildHeader(...), которая возвращает многострочный заголовок.

fun buildHeader(runId: String, info: String): String {
    val sb = StringBuilder()
    sb.appendLine("=== Text Analyzer Report ===")
    sb.appendLine("runId: $runId")
    sb.appendLine("env:   $info")
    return sb.toString()
}

Метод appendLine добавляет строку и перевод строки. Если у вас его нет (в зависимости от окружения), можно заменить на append("...\n"), идея та же.

Где import не нужен — и почему это нормально

Новичков часто сбивает с толку, что часть Java‑вещей мы импортируем, а часть — нет. Например, System мы не импортировали. Это не потому, что «так захотелось», а потому что на JVM некоторые пакеты подключаются автоматически (в частности, java.lang). Но на уровне практики правило простое: если компилятор не видит имя — добавьте import (или временно напишите полное имя класса) и живите дальше.

Для тренировки можете сами переписать System.getProperty как java.lang.System.getProperty — оно будет работать так же.

Используем Java ArrayList как «буфер строк» перед финальным выводом

Иногда удобно строить отчёт не одной строкой, а списком строк: добавил строку — потом объединил. Это подход «буферизации», и он встречается в старом Java‑коде сплошь и рядом. Мы сделаем это нарочно, чтобы вы почувствовали стиль Java API: создали изменяемую структуру, наполнили, затем преобразовали.

import java.util.ArrayList

fun buildLines(): ArrayList<String> {
    val lines = ArrayList<String>()
    lines.add("line 1")
    lines.add("line 2")
    return lines
}

fun main() {
    println(buildLines().size) // 2
}

Смысл не в том, что так «лучше, чем Kotlin». Смысл в том, что так часто бывает в API, с которым вы столкнётесь, и вам нужно чувствовать себя уверенно.

Собираем всё вместе: мини‑main нашего анализатора

Теперь склеим идеи в один маленький сценарий: генерируем runId, берём runtimeInfo, печатаем заголовок, затем читаем строку текста и выводим длину (как простейшую «заглушку анализа»). Мы не будем заново писать токенизацию и частоты — они у вас уже есть из предыдущих дней.

import java.util.UUID

fun main() {
    val runId = UUID.randomUUID().toString()
    val info = runtimeInfo()
    println(buildHeader(runId, info))

    val text = readln()
    println("chars: ${text.length}") // chars: 12
}

Если вы сейчас подумали «подождите, runtimeInfo() и buildHeader() где-то выше, а тут их нет» — всё правильно: это один проект, просто функции лежат в том же файле или импортируются из другого. Мы продолжаем развивать приложение постепенно, как и делали раньше.

6. Схема: Kotlin → JVM → JDK

После первых встреч с Java API бывает ощущение, что Kotlin «внезапно стал Java». На самом деле он просто стоит на плечах JVM и использует JDK как стандартный набор инструментов. Удобно представить это в виде простой блок‑схемы: вы пишете Kotlin, он компилируется, а во время выполнения рядом лежат классы JDK, которые вы спокойно вызываете.

flowchart TD
    A[Kotlin код] --> B[Компилятор Kotlin]
    B --> C[байткод JVM]
    C --> D[JVM]
    D --> E[JDK / java.* классы]
    D --> F[Ваше приложение: анализатор текста]
    E --> F

Эта схема важна психологически: вы не «подключаете Java отдельно». Вы уже внутри мира JVM — просто начали осознанно брать оттуда полезные вещи.

7. Типичные ошибки при вызове Java API из Kotlin

Ошибка №1: забыли import и решили, что «в Kotlin нет такого класса».
Чаще всего класс есть, просто компилятор не знает, откуда его взять. Если вы видите Unresolved reference, попробуйте сначала написать полное имя (java.util.UUID), а затем нажмите Alt+Enter в IDE и добавьте import. Это самый быстрый способ перестать гадать и начать жить.

Ошибка №2: перепутали вызов static‑метода и метода объекта.
Если вы пишете UUID.toString() и ждёте строку, компилятор будет не в восторге: toString() — это метод объекта, а не класса. Правильная цепочка: сначала получить объект (например, UUID.randomUUID()), а потом вызвать у него метод (.toString()).

Ошибка №3: ожидают, что Java‑коллекции «ведут себя как read‑only List».
Если вы создали ArrayList и передали его куда-то, другой код может его изменить — и это не баг, а стиль Java. На практике это означает, что с Java‑объектами нужно внимательнее относиться к мутабельности: если вам важен снимок данных, вы обычно делаете копию (сейчас достаточно просто понимать проблему).

Ошибка №4: пытаются «лечить» проблему через копипаст полного имени везде.
Полные имена удобны для разового примера, но превращают реальный код в простыню. Если вы используете класс больше одного раза, лучше честно добавить import. Код станет короче, и вы начнёте видеть логику программы, а не адреса домов на улице java.util.

Ошибка №5: думают, что Java API «не для Kotlin», и избегают его даже там, где оно упрощает жизнь.
Парадоксально, но иногда новичок принципиально не хочет трогать System или UUID, потому что «это Java». На JVM это не «чужое», а стандартная библиотека среды выполнения. Если задача решается проще через JDK — берите JDK и не мучайте себя (и будущего читателя кода).

1
Задача
Kotlin SELF, 29 уровень, 0 лекция
Недоступна
Идентификатор запуска
Идентификатор запуска
1
Задача
Kotlin SELF, 29 уровень, 0 лекция
Недоступна
Паспорт окружения
Паспорт окружения
1
Задача
Kotlin SELF, 29 уровень, 0 лекция
Недоступна
Визитка в чате
Визитка в чате
1
Задача
Kotlin SELF, 29 уровень, 0 лекция
Недоступна
Чистка списка
Чистка списка
Комментарии
ЧТОБЫ ПОСМОТРЕТЬ ВСЕ КОММЕНТАРИИ ИЛИ ОСТАВИТЬ КОММЕНТАРИЙ,
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ