Как настроить interoperability между Kotlin и Java в Gradle?

Интероперабельность (interoperability) между Kotlin и Java в Gradle-проекте — это возможность свободного взаимодействия кода на Kotlin и Java в одном Android или JVM проекте. Это одна из главных причин, по которой Kotlin так легко внедряется в существующие Java-кодовые базы. Kotlin специально проектировался с высокой совместимостью с Java и их экосистемами, включая инструменты сборки, такие как Gradle.

1. Совместное использование исходных файлов Java и Kotlin

Для начала в проекте должны присутствовать как Java, так и Kotlin исходники. Стандартная структура проекта может выглядеть так:

src/
├── main/
 ├── java/  Java-файлы
 └── kotlin/  Kotlin-файлы

Gradle умеет компилировать Java и Kotlin в нужной последовательности. Kotlin компилируется после Java, чтобы видеть классы, скомпилированные на Java.

2. Подключение Kotlin плагина в build.gradle.kts (или build.gradle)

В build.gradle.kts (Kotlin DSL):

plugins {
kotlin("jvm") version "1.9.0" // или kotlin("android") для Android-проекта
}

Или в build.gradle (Groovy DSL):

plugins {
id 'org.jetbrains.kotlin.jvm' version '1.9.0'
}

3. Указание исходных директорий

Gradle автоматически включает директории src/main/kotlin и src/main/java. Однако, если вы используете нестандартную структуру, нужно указать пути вручную:

sourceSets {
main {
java.srcDirs("src/main/java", "src/main/kotlin")
}
}

4. Настройка зависимостей Kotlin

В секции зависимостей:

dependencies {
implementation(kotlin("stdlib")) // или stdlib-jdk8 для JDK 8+
}

Для Android-проектов:

dependencies {
implementation("org.jetbrains.kotlin:kotlin-stdlib:1.9.0")
}

5. Настройка kotlinOptions

Добавьте:

tasks.withType<org.jetbrains.kotlin.gradle.tasks.KotlinCompile> {
kotlinOptions {
jvmTarget = "1.8" // или "11" или выше
}
}

Это нужно для корректной компиляции Kotlin в байткод совместимый с JVM версии 8 и выше, и для его корректной работы с Java-кодом.

6. Использование аннотаций для совместимости

Иногда Kotlin-функции или свойства нужно делать совместимыми с Java. Для этого используются аннотации:

  • @JvmStatic — чтобы сделать метод статическим в Java.

  • @JvmOverloads — для генерации перегруженных методов.

  • @JvmField — чтобы обратиться к полю без геттеров/сеттеров.

  • @file:JvmName("MyUtils") — чтобы задать имя класса для file-level функций.

7. Java-код вызывает Kotlin

Java может напрямую обращаться к Kotlin-классам, функциям и свойствам, как если бы они были написаны на Java. Однако есть нюансы:

  • У Kotlin-свойств нет стандартных setField() — нужны getX() / setX() методы.

  • В Java нужно использовать .INSTANCE для обращения к объекту object.

  • Companion-объекты в Kotlin из Java вызываются через MyClass.Companion.method() (или @JvmStatic, чтобы это упростить).

8. Kotlin вызывает Java

Здесь ограничений почти нет. Kotlin может:

  • Наследовать Java-классы

  • Вызывать методы, конструкторы, поля

  • Работать с аннотациями и лямбдами

  • Использовать SAM-конверсии (Single Abstract Method)

9. Настройка Kotlin и Java в settings.gradle или multi-module проектах

Если проект состоит из нескольких модулей, каждый из которых использует Kotlin и Java, следует в каждом модуле подключить Kotlin плагин, и убедиться, что kotlin("stdlib") и kotlinOptions.jvmTarget заданы везде.

Пример:

allprojects {
repositories {
google()
mavenCentral()
}
}
subprojects {
plugins.withId("org.jetbrains.kotlin.jvm") {
dependencies {
"implementation"(kotlin("stdlib"))
}
tasks.withType<org.jetbrains.kotlin.gradle.tasks.KotlinCompile> {
kotlinOptions.jvmTarget = "1.8"
}
}
}

10. Пример простого проекта с Kotlin и Java

Main.kt

fun main() {
val greeter = Greeter()
greeter.sayHello("Kotlin")
}

Greeter.java

public class Greeter {
public void sayHello(String name) {
System.out.println("Hello, " + name + " from Java!");
}
}

Gradle соберёт оба файла и запустит программу без проблем.