JavaRush /Курси /Kotlin SELF /Kotlin на JVM: як викликати Java API

Kotlin на JVM: як викликати Java API

Kotlin SELF
Рівень 29 , Лекція 0
Відкрита

1. Kotlin/JVM і стандартна бібліотека Java: що взагалі відбувається

Коли ви пишете 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. Конструктори та «статичні» методи 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 «статичні» методи: виклик через імʼя типу

У 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): там список доступний лише для читання (за посиланням), а тут — «живий» і такий, що змінюється.

Навіщо взагалі чіпати Java‑колекції, якщо є Kotlin‑колекції?

У цій лекції ми не заглиблюватимемося в тонкощі сумісності (це окрема велика тема). Але практична причина проста: величезна кількість API у світі JVM історично приймає й повертає Java‑колекції. Тож якщо ви хочете впевнено користуватися бібліотеками, вам потрібно принаймні не лякатися ArrayList.

5. Міні‑проєкт: «Text Analyzer» дорослішає

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

Щоб приклади склалися в єдине ціле, вважатимемо, що в нас уже є спрощений аналізатор тексту (у стилі лекцій про пайплайни): токенізація, підрахунок частот, виведення top‑N.

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

У довгих сценаріях корисно вміти сказати: «Оцей звіт стосується конкретного запуску застосунку». Особливо якщо користувач надсилає вам скриншот або фрагмент лога. UUID — це просто унікальний рядок, який легко згенерувати й майже неможливо випадково повторити.

Зробимо функцію newRunId(), яка повертає рядок. Усередині використаємо Java‑клас UUID та його «статичний» метод.

import java.util.UUID

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

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

Ми поки не обговорюємо «чому так влаштовано» — зараз важливіше інше: ви побачили, що Java «статичний» метод у 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 ===")
    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("рядок 1")
    lines.add("рядок 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». Насправді Kotlin просто стоїть на плечах 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: переплутали виклик «статичного» методу й методу обʼєкта.
Якщо ви пишете UUID.toString() і очікуєте рядок, компілятор буде не в захваті: toString() — це метод обʼєкта, а не класу. Правильний ланцюжок такий: спочатку отримати обʼєкт (наприклад, UUID.randomUUID()), а потім викликати в нього метод (.toString()).

Помилка №3: очікують, що Java‑колекції «поводяться як список лише для читання List».
Якщо ви створили ArrayList і передали його кудись, інший код може його змінити — і це не баг, а стиль Java. На практиці це означає, що з Java‑обʼєктами треба уважніше ставитися до змінюваності: якщо вам важливий «знімок» даних, зазвичай роблять копію (зараз достатньо просто розуміти проблему).

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

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

Коментарі
ЩОБ ПОДИВИТИСЯ ВСІ КОМЕНТАРІ АБО ЗАЛИШИТИ КОМЕНТАР,
ПЕРЕЙДІТЬ В ПОВНУ ВЕРСІЮ