Урок по треку Kotlin 6 мин

Java-интероп за 6 минут

@JvmStatic, @JvmOverloads, @JvmField, платформенные типы, SAM-конверсия и исключения.

Почти любой Kotlin-проект живёт рядом с Java: Android SDK, Spring, старые модули, библиотеки. На собесе спрашивают, как Kotlin видит Java, как Java видит Kotlin и где на этом стыке ломается код.

Java из Kotlin

Геттеры и сеттеры превращаются в свойства: user.getName() пишется как user.name, isActive() как user.isActive.

Платформенные типы. Если Java-метод не размечен аннотациями nullability, Kotlin видит тип String!: можно присвоить и в String, и в String?. Компилятор не проверяет, ответственность ваша. Аннотации @Nullable/@NonNull (JetBrains, AndroidX, JSpecify) превращают их в нормальные типы.

val name: String = javaUser.name   // NPE здесь, если Java вернула null
val safe: String? = javaUser.name  // безопасно

SAM-конверсия. Интерфейс Java с одним абстрактным методом принимает лямбду: executor.execute { work() }, view.setOnClickListener { }.

Ключевые слова Kotlin в Java-именах экранируются обратными кавычками: System.\in`, mockito.`when`(...)`.

Checked-исключения. В Kotlin их нет: можно вызвать Files.readString(path) без try и без throws.

Kotlin из Java

// Utils.kt
@file:JvmName("Utils")
package app

fun slug(s: String) = s.lowercase()

class User(val name: String) {
    companion object {
        @JvmStatic fun guest() = User("Гость")
        const val MAX = 10
        @JvmField val DEFAULT_NAME = "Гость"
    }
}
KotlinКак видит Java
Функция верхнего уровня в Utils.ktUtilsKt.slug(...), с @file:JvmName("Utils") будет Utils.slug(...)
val namegetName(); var ещё и setName()
companion object { fun guest() }User.Companion.guest(); с @JvmStatic будет User.guest()
@JvmField val xпубличное поле без геттера
const val MAXpublic static final константа
object ConfigConfig.INSTANCE
Параметры по умолчаниюодин метод со всеми параметрами; @JvmOverloads генерирует перегрузки
fun f(): Unitvoid f()
Свойство isActiveisActive() и setActive()

Checked-исключения и Java. Kotlin-функция, бросающая IOException, для Java не объявляет throws, и Java-код не сможет написать catch (IOException e): компилятор Java скажет, что оно не бросается. Решение: @Throws(IOException::class).

Null-проверки на границе. У публичных функций Kotlin с non-null параметрами компилятор вставляет проверку Intrinsics.checkNotNullParameter. Если Java передаст null, упадёт сразу на входе с понятным сообщением, а не глубже.

suspend-функции для Java выглядят как метод с дополнительным параметром Continuation, вызывать их из Java неудобно. Для Java-клиентов делают обёртки, возвращающие CompletableFuture (future { } из kotlinx-coroutines-jdk8, сейчас в core).

Дженерики и вариантность. Kotlin генерирует wildcards в сигнатурах для параметров: List<out T> в параметре станет List<? extends T>. Управлять этим можно аннотациями @JvmWildcard и @JvmSuppressWildcards (важно для Dagger/Hilt, когда multibinding не находит Set<Plugin>).

internal в байткоде становится public с искажённым именем (mangling), Java его увидит, но с неудобным именем.

Коллекции

Kotlin List и MutableList это один и тот же java.util.List. Java-метод, получив ваш «read-only» список, спокойно его изменит (если реализация изменяемая). Если важно, отдавайте копию.

Частые ошибки

  • Присвоить платформенный тип в non-null переменную без проверки.
  • Забыть @JvmStatic/@JvmOverloads/@Throws для Java-клиентов.
  • Ждать, что List защитит от изменений из Java.
  • Ловить в Java исключение, которое Kotlin-функция не объявила.

Что спросят на собесе

  • Что такое платформенный тип и как от него защититься?
  • Как вызвать функцию из companion object как статическую?
  • Зачем @JvmOverloads, @JvmField, @JvmName, @Throws?
  • Как Kotlin обходится без checked-исключений и что это значит для Java-клиентов?
  • Как вызвать suspend-функцию из Java?

Закрепите за 5 минут

После урока в тренажёре 3-5 проверочных вопросов, дальше модуль уходит в повторения.