Как Kotlin работает с Java-кодом?

Kotlin полностью совместим с Java, что делает его мощным инструментом для разработки Android-приложений и облегчает постепенную миграцию существующего Java-кода. Компилятор Kotlin транслирует код в байт-код JVM, совместимый с Java-классами, что позволяет проектам использовать Kotlin и Java одновременно без дополнительных настроек. Благодаря этой совместимости, Kotlin может вызывать Java-код и наоборот, что особенно полезно при работе с устоявшимися Java-библиотеками или SDK Android.

Двунаправленная совместимость

Kotlin может:

  • Использовать Java-классы, интерфейсы, перечисления, аннотации и исключения.

  • Наследовать от Java-классов и реализовывать Java-интерфейсы.

  • Вызывать Java-методы, включая перегруженные.

  • Обрабатывать null-значения через систему типов Kotlin.

Java может:

  • Создавать экземпляры Kotlin-классов.

  • Вызывать Kotlin-функции, включая extension-функции (если они сгенерированы как статические).

  • Получать доступ к свойствам Kotlin через геттеры и сеттеры.

  • Обрабатывать @JvmName, @JvmOverloads, @JvmStatic, @JvmField и другие аннотации, упрощающие интеграцию.

Использование Java-кода в Kotlin

Kotlin может напрямую использовать Java-библиотеки без дополнительных обёрток. Например:

// Java-код
public class JavaUser {
private String name;
public JavaUser(String name) {
this.name = name;
}
public String getName() { return name; }
public void printUser() {
System.out.println("User: " + name);
}
}
// Kotlin-код
val user = JavaUser("Alice")
println(user.name) // вызов getName()
user.printUser()

Null-совместимость

Kotlin строго разделяет nullable и non-null типы. Однако Java не содержит этой информации, поэтому Kotlin использует платформенные типы T!:

// Java
public String getEmail() { return null; }
val email1: String = javaObject.email // компилятор не ругается, но это опасно
val email2: String? = javaObject.email // безопасно

Чтобы Kotlin корректно работал с null-опасными Java-методами, программист сам указывает безопасный или небезопасный вызов (!!, ?., ?:).

Расширения Kotlin для Java

Kotlin предоставляет дополнительные возможности, при работе с Java-классами:

  • Extension-функции: можно "добавлять" методы к Java-классам без изменения их исходников:

fun File.readTextSafe(): String = if (exists()) readText() else ""

  • Lambdas: Java-функциональные интерфейсы можно передавать в Kotlin как лямбды:
// Java
public interface Callback {
void onDone();
}
fun doSomething(callback: Callback) {
callback.onDone()
}
doSomething { println("Done!") } // SAM-конверсия
  • Коллекции: Kotlin использует обёртки над List, Map и Set из Java, добавляя методы filter, map, firstOrNull и др.

Kotlin-аннотации для Java-интероперабельности

Kotlin предоставляет ряд аннотаций, улучшающих совместимость Kotlin-классов при использовании их из Java:

@JvmOverloads

Генерирует перегруженные методы для функций с параметрами по умолчанию:

@JvmOverloads
fun greet(name: String = "User", age: Int = 0) { ... }

Java может вызывать:

greet();
greet("Tom");
greet("Tom", 30);

@JvmStatic

Объявляет статическую функцию внутри object или companion object, делая её доступной как статический метод из Java:

object Utils {
@JvmStatic
fun doWork() { ... }
}

Java:

Utils.doWork();

@JvmField

Позволяет Java напрямую обращаться к полю без использования getter/setter:

class Constants {
companion object {
@JvmField
val VERSION = "1.0"
}
}

Java:

String v = Constants.VERSION;

@JvmName

Изменяет имя функции или файла для вызова из Java:

@file:JvmName("MyUtils") // файл Utils.kt
fun doSomething() {}

Java:

MyUtils.doSomething();

@Throws

Указывает Kotlin-компилятору сгенерировать throws-директиву для Java:

@Throws(IOException::class)
fun readFile() { ... }

Java увидит:

void readFile() throws IOException;

Kotlin в существующих Java-проектах

Можно добавлять Kotlin в существующий Java-проект поэтапно:

Добавить Kotlin-плагин и зависимости в Gradle:

plugins {
id 'org.jetbrains.kotlin.android'
}
dependencies {
implementation "org.jetbrains.kotlin:kotlin-stdlib"
}
  1. Создать Kotlin-файл (например, Main.kt), и он может использовать любые Java-классы проекта.

  2. Java-файлы продолжают работать, как раньше.

Kotlin-код из Java

Kotlin-классы видны из Java как обычные классы, но с рядом особенностей:

  • Свойства Kotlin отображаются как методы getX(), setX().

  • Топ-левел-функции Kotlin размещаются в статическом классе с именем файла (UtilsKt.class).

  • Компаньон-объекты доступны через Companion.

class User(val name: String) {
companion object {
fun create(): User = User("New")
}
}

Java:

User user = User.Companion.create();

Чтобы сделать метод статическим:

companion object {
@JvmStatic fun create() = User("New")
}

Java:

User user = User.create();

Что нельзя напрямую

  • Java не понимает extension-функции — их нужно вызывать как статические методы.

  • Java не различает nullable/non-nullable типы Kotlin.

  • Kotlin-корутины недоступны напрямую из Java (требуются обёртки через CompletableFuture или коллбэки).

  • Kotlin inline-классы, sealed-интерфейсы, value-классы могут быть неполноценно видимы из Java.

Совместное использование Kotlin и Java в Android

В Android-проектах обычно:

  • Используются Java-библиотеки Android SDK и сторонние библиотеки.

  • ViewModel, LiveData, Retrofit и Room могут быть написаны на Java или Kotlin и использоваться из обоих языков.

  • Интерфейсы (например, слушатели) часто реализуются через лямбды.

  • Переход с Java на Kotlin делают по одному классу, комбинируя kt и java файлы в одном модуле.