Spec-Zone.ru › Kotlin 2

Вызов Java из Kotlin

Kotlin разработан с учетом взаимодействия с Java. Существующий код Java можно естественным образом вызывать из Kotlin, а код Kotlin также довольно просто использовать из Java. В этом разделе мы рассмотрим некоторые особенности вызова кода Java из Kotlin.

Практически любой код Java можно использовать без каких-либо проблем:

import java.util.*

fun demo(source: List<Int>) {
    val list = ArrayList<Int>()
    // 'for'-loops work for Java collections:
    for (item in source) {
        list.add(item)
    }
    // Operator conventions work as well:
    for (i in 0..source.size - 1) {
        list[i] = source[i] // get and set are called
    }
}

Геттеры и сеттеры

Методы, соответствующие соглашениям Java о геттерах и сеттерах (методы без аргументов, имена которых начинаются с get, и методы с одним аргументом, имена которых начинаются с set), представлены в Kotlin как свойства. Такие свойства также называются синтетическими свойствами. Boolean методы доступа (где имя геттера начинается с is, а имя сеттера — с set) представлены как свойства с тем же именем, что и метод-геттер.

import java.util.Calendar

fun calendarDemo() {
    val calendar = Calendar.getInstance()
    if (calendar.firstDayOfWeek == Calendar.SUNDAY) { // call getFirstDayOfWeek()
        calendar.firstDayOfWeek = Calendar.MONDAY // call setFirstDayOfWeek()
    }
    if (!calendar.isLenient) { // call isLenient()
        calendar.isLenient = true // call setLenient()
    }
}

Приведенный выше calendar.firstDayOfWeek — пример синтетического свойства.

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

Ссылки на синтетические свойства Java

Эта возможность является экспериментальной. В любой момент ее могут удалить или изменить. Рекомендуем использовать ее только для ознакомительной оценки.

Начиная с Kotlin 1.8.20, можно создавать ссылки на синтетические свойства Java. Рассмотрим следующий код Java:

public class Person {
    private String name;
    private int age;

    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public String getName() {
        return name;
    }

    public int getAge() {
        return age;
    }
}

Kotlin всегда позволял писать person.age, где age — синтетическое свойство. Теперь можно также создавать ссылки на Person::age и person::age. То же самое применимо и к name.

val persons = listOf(Person("Jack", 11), Person("Sofie", 12), Person("Peter", 11))
    persons
         // Call a reference to Java synthetic property:
        .sortedBy(Person::age)
         // Call Java getter via the Kotlin property syntax:
        .forEach { person -> println(person.name) }

Как включить ссылки на синтетические свойства Java

Чтобы включить эту возможность, задайте параметр компилятора -language-version 2.1. В проекте Gradle это можно сделать, добавив следующий код в build.gradle(.kts):

tasks
    .withType<org.jetbrains.kotlin.gradle.tasks.KotlinCompilationTask<*>>()
    .configureEach {
        compilerOptions
            .languageVersion
            .set(
                org.jetbrains.kotlin.gradle.dsl.KotlinVersion.KOTLIN_2_1
            )
    }
tasks
    .withType(org.jetbrains.kotlin.gradle.tasks.KotlinCompilationTask.class)
    .configureEach {
        compilerOptions.languageVersion
            = org.jetbrains.kotlin.gradle.dsl.KotlinVersion.KOTLIN_2_1
}

До Kotlin 1.9.0 для включения этой возможности требовалось задать параметр компилятора -language-version 1.9.

Методы, возвращающие void

Если метод Java возвращает void, при вызове из Kotlin он вернет Unit. Если кто-либо использует это возвращаемое значение, компилятор Kotlin присвоит его в месте вызова, поскольку само значение известно заранее (так как оно равно Unit).

Экранирование идентификаторов Java, являющихся ключевыми словами Kotlin

Некоторые ключевые слова Kotlin являются допустимыми идентификаторами в Java: in, object, is и другие. Если в библиотеке Java ключевое слово Kotlin используется в качестве имени метода, его все равно можно вызвать, экранировав обратными кавычками (`):

foo.`is`(bar)

Безопасность относительно null и платформенные типы

Любая ссылка в Java может быть null, из-за чего требования Kotlin к строгой безопасности относительно null непрактичны для объектов, полученных из Java. Типы объявлений Java рассматриваются в Kotlin как невыразимые и называются платформенными типами. Невыразимые типы нельзя явно записать в коде. Поэтому при присваивании платформенного значения переменной Kotlin можно:

  • Положиться на вывод типов. В этом случае переменной будет присвоен выведенный платформенный тип.

  • Выбрать предполагаемый тип. Kotlin допускает как nullable-, так и non-nullable-типы.

Проверки на null для таких типов ослаблены, поэтому гарантии безопасности для них такие же, как в Java (подробнее см. ниже). Рассмотрим следующие примеры:

val list = ArrayList<String>() // non-null (constructor result)
list.add("Item")
val size = list.size // non-null (primitive int)
val item = list[0] // platform type inferred (ordinary Java object)

При вызове методов у переменных платформенных типов Kotlin не сообщает об ошибках nullability во время компиляции, однако вызов может завершиться ошибкой во время выполнения из-за исключения о разыменовании null-указателя или проверки, которую Kotlin генерирует, чтобы предотвратить распространение null:

item.substring(1) // allowed, throws an exception if item == null

Значение платформенного типа можно присвоить переменной как nullable-, так и non-nullable-типа Kotlin. Однако если присвоить такое значение переменной non-nullable-типа, а во время выполнения значение окажется null, Kotlin выбросит NullPointerException. Чтобы этого избежать, явно укажите nullability в коде Kotlin:

val nullable: String? = item // allowed, always works
val notNull: String = item // allowed, may fail at runtime

Если выбрать non-nullable-тип, компилятор добавит проверку при присваивании. Это не позволит хранить null в non-nullable-переменных Kotlin. Проверки также добавляются при передаче платформенных значений функциям Kotlin, ожидающим non-null-значения, и в других случаях. В целом компилятор делает все возможное, чтобы не допустить распространения null по программе, хотя иногда полностью устранить его невозможно из-за обобщений.

Обозначение платформенных типов

Как упоминалось в предыдущем разделе, платформенные типы нельзя явно указать в программе, поэтому в языке для них нет синтаксиса. Тем не менее компилятору и IDE иногда нужно отображать их (например, в сообщениях об ошибках или информации о параметрах), поэтому для них используется мнемоническое обозначение:

  • T! означает « T или T? »

  • (Mutable)Collection<T>! означает «коллекция Java из T может быть изменяемой или нет, nullable или нет»

  • Array<(out) T>! означает «массив Java из T (или подтипа T), nullable или нет»

Если вы видите это обозначение в сообщении об ошибке или подсказке IDE, добавьте явную аннотацию типа для переменной Kotlin, чтобы восстановить проверки безопасности относительно null, или устраните платформенные типы в исходном коде с помощью аннотаций nullability.

Аннотации nullability

Типы Java с аннотациями nullability представлены не платформенными типами, а обычными nullable- или non-nullable-типами Kotlin. Компилятор поддерживает несколько разновидностей аннотаций nullability, в том числе:

  • JetBrains (@Nullable и @NotNull из пакета org.jetbrains.annotations)

  • JSpecify (org.jspecify.annotations)

  • Android (com.android.annotations и android.support.annotations)

  • JSR-305 (javax.annotation)

  • FindBugs (edu.umd.cs.findbugs.annotations)

  • Eclipse (org.eclipse.jdt.annotation)

  • Lombok (lombok.NonNull)

  • RxJava 3 (io.reactivex.rxjava3.annotations)

  • Vert.x (io.vertx.codegen.annotations)

Можно указать компилятору сообщать о несоответствиях nullability для определенных аннотаций nullability с помощью следующего параметра компилятора:

-Xnullability-annotations=@<package-name>:<report-level>

Укажите имя пакета для полностью квалифицированных аннотаций nullability и один из следующих уровней сообщений:

  • ignore — игнорировать несоответствия nullability

  • warn — выдавать предупреждения

  • strict — выдавать ошибки.

JSpecify — единственная поддерживаемая разновидность, в которой по умолчанию используется уровень сообщений strict. Ее можно использовать для выдачи ошибок по аннотациям nullability без дополнительной настройки.

Полный список поддерживаемых аннотаций nullability см. в исходном коде компилятора Kotlin.

Аннотации изменяемости

К объявлению Java можно добавить аннотацию изменяемости, чтобы указать, является ли возвращаемая коллекция доступной только для чтения или изменяемой в Kotlin. Если присвоить значение типу коллекции с другой изменяемостью, компилятор сообщит о несоответствии типов. Степень серьезности диагностики зависит от конкретной аннотации изменяемости.

Компилятор поддерживает несколько аннотаций изменяемости, в том числе:

  • kotlin.annotations.jvm.ReadOnly

  • kotlin.annotations.jvm.Mutable

  • org.jetbrains.annotations.Unmodifiable

  • org.jetbrains.annotations.UnmodifiableView

Полный список поддерживаемых аннотаций изменяемости см. в исходном коде компилятора Kotlin.

Аннотирование аргументов и параметров типов

Можно аннотировать аргументы и параметры типов обобщенных типов, чтобы также указать информацию об их nullability.

Во всех примерах этого раздела используются аннотации nullability JetBrains из пакета org.jetbrains.annotations.

Аргументы типов

Рассмотрим следующие аннотации в объявлении Java:

@NotNull
Set<@NotNull String> toSet(@NotNull Collection<@NotNull String> elements) { ... }

В Kotlin им соответствует следующая сигнатура:

fun toSet(elements: (Mutable)Collection<String>) : (Mutable)Set<String> { ... }

Если в аргументе типа отсутствует аннотация @NotNull, вместо нее используется платформенный тип:

fun toSet(elements: (Mutable)Collection<String!>) : (Mutable)Set<String!> { ... }

Kotlin также учитывает аннотации nullability у аргументов типов базовых классов и интерфейсов. Например, имеются два класса Java со следующими сигнатурами:

public class Base<T> {}
public class Derived extends Base<@Nullable String> {}

В коде Kotlin передача экземпляра Derived в место, где ожидается Base<String>, приводит к предупреждению.

fun takeBaseOfNotNullStrings(x: Base<String>) {}

fun main() {
    takeBaseOfNotNullStrings(Derived()) // warning: nullability mismatch
}

Верхняя граница Derived задается как Base<String?>, что отличается от Base<String>.

Подробнее см. раздел Обобщения Java в Kotlin.

Параметры типов

По умолчанию nullability обычных параметров типов не определена ни в Kotlin, ни в Java. В Java ее можно указать с помощью аннотаций nullability. Добавим аннотацию параметру типа класса Base:

public class Base<@NotNull T> {}

При наследовании от Base Kotlin ожидает аргумент или параметр типа non-nullable. Поэтому следующий код Kotlin приводит к предупреждению:

class Derived<K> : Base<K> {} // warning: K has undefined nullability

Это можно исправить, указав верхнюю границу K : Any.

Kotlin также поддерживает аннотации nullability для границ параметров типов Java. Добавим границы для Base:

public class BaseWithBound<T extends @NotNull Number> {}

Kotlin преобразует это следующим образом:

class BaseWithBound<T : Number> {}

Таким образом, передача nullable-типа в качестве аргумента или параметра типа приводит к предупреждению.

Аннотирование аргументов и параметров типов работает с целевой версией Java 8 или выше. Для этой возможности требуется, чтобы аннотации nullability поддерживали цель TYPE_USE (org.jetbrains.annotations поддерживает ее начиная с версии 15).

Если аннотация nullability поддерживает другие цели, применимые к типу, помимо цели TYPE_USE, приоритет имеет TYPE_USE. Например, если у @Nullable есть цели TYPE_USE и METHOD, сигнатура метода Java @Nullable String[] f() преобразуется в Kotlin как fun f(): Array<String?>!.

Поддержка JSpecify

Kotlin поддерживает аннотации nullability JSpecify, которые предоставляют единый набор аннотаций для nullability в Java. JSpecify позволяет указывать подробную информацию о nullability объявлений Java и помогает Kotlin сохранять безопасность относительно null при работе с кодом Java.

Kotlin поддерживает следующие аннотации из пакета org.jspecify.annotations:

  • @Nullable помечает тип как nullable.

  • @NonNull помечает тип как non-nullable.

  • @NullMarked помечает все типы в области видимости, например в классе или пакете, как non-nullable по умолчанию, если не указано иное.

    Эта аннотация не применяется к локальным переменным и переменным типов (обобщениям). Переменные типов остаются «не зависящими от null», пока не будет указан конкретный nullable- или non-nullable-тип.

  • @NullUnmarked отменяет действие @NullMarked, превращая все типы в области видимости в платформенные типы.

Рассмотрим следующий класс Java с аннотациями JSpecify:

// Java
import org.jspecify.annotations.*;

@NullMarked
public class InventoryService {
    public String notNull() { return ""; }
    public @Nullable String nullable() { return null; }
}

В Kotlin они рассматриваются как обычные nullable- и non-nullable-типы, а не как платформенные типы:

// Kotlin
fun test(inventory: InventoryService) {
   inventory.notNull().length // OK
   inventory.nullable().length // Error: only safe (?.) or non-null asserted (!!) calls are allowed
}

По умолчанию компилятор Kotlin сообщает об ошибках несоответствия nullability для аннотаций JSpecify. Степень серьезности диагностики nullability JSpecify можно настроить с помощью следующего параметра компилятора:

-Xjspecify-annotations=<report-level>

Доступны следующие уровни сообщений:

Уровень

Описание

strict

Сообщать об ошибках при несоответствии nullability (по умолчанию).

warn

Сообщать о предупреждениях.

ignore

Игнорировать несоответствия nullability.

Дополнительную информацию об аннотациях JSpecify см. в руководстве пользователя JSpecify.

Поддержка JSR-305

Аннотация @Nonnull, определенная в JSR-305, поддерживается для обозначения nullability типов Java.

Если значение @Nonnull(when = ...) равно When.ALWAYS, аннотированный тип считается non-nullable; When.MAYBE и When.NEVER обозначают nullable-тип; а When.UNKNOWN принудительно задает тип как платформенный.

Библиотеку можно скомпилировать с аннотациями JSR-305, но добавлять артефакт с аннотациями (например, jsr305.jar) в зависимости компиляции для пользователей библиотеки не требуется. Компилятор Kotlin может считывать аннотации JSR-305 из библиотеки, даже если сами аннотации отсутствуют в classpath.

Пользовательские квалификаторы nullability (KEEP-79) также поддерживаются (см. ниже).

Псевдонимы квалификаторов типов

Если тип аннотации аннотирован одновременно @TypeQualifierNickname и @Nonnull JSR-305 (или другим ее псевдонимом, например @CheckForNull), такой тип аннотации используется для получения точной nullability и имеет тот же смысл, что и соответствующая аннотация nullability:

@TypeQualifierNickname
@Nonnull(when = When.ALWAYS)
@Retention(RetentionPolicy.RUNTIME)
public @interface MyNonnull {
}

@TypeQualifierNickname
@CheckForNull // a nickname to another type qualifier nickname
@Retention(RetentionPolicy.RUNTIME)
public @interface MyNullable {
}

interface A {
    @MyNullable String foo(@MyNonnull String x);
    // in Kotlin (strict mode): `fun foo(x: String): String?`

    String bar(List<@MyNonnull String> x);
    // in Kotlin (strict mode): `fun bar(x: List<String>!): String!`
}

Значения по умолчанию для квалификаторов типов

@TypeQualifierDefault позволяет вводить аннотации, применение которых задает nullability по умолчанию в области действия аннотированного элемента.

Сам тип такой аннотации должен быть аннотирован одновременно @Nonnull (или ее псевдонимом) и @TypeQualifierDefault(...) с одним или несколькими значениями ElementType:

  • ElementType.METHOD — для возвращаемых типов методов

  • ElementType.PARAMETER — для параметров-значений

  • ElementType.FIELD — для полей

  • ElementType.TYPE_USE — для любого типа, включая аргументы типов, верхние границы параметров типов и типы с подстановочными знаками

Nullability по умолчанию применяется, если сам тип не аннотирован аннотацией nullability. Значение по умолчанию определяется ближайшим внешним элементом, аннотированным аннотацией со значением по умолчанию для квалификатора типа, у которой ElementType соответствует использованию типа.

@Nonnull
@TypeQualifierDefault({ElementType.METHOD, ElementType.PARAMETER})
public @interface NonNullApi {
}

@Nonnull(when = When.MAYBE)
@TypeQualifierDefault({ElementType.METHOD, ElementType.PARAMETER, ElementType.TYPE_USE})
public @interface NullableApi {
}

@NullableApi
interface A {
    String foo(String x); // fun foo(x: String?): String?

    @NotNullApi // overriding default from the interface
    String bar(String x, @Nullable String y); // fun bar(x: String, y: String?): String

    // The List<String> type argument is seen as nullable because of `@NullableApi`
    // having the `TYPE_USE` element type:
    String baz(List<String> x); // fun baz(List<String?>?): String?

    // The type of `x` parameter remains platform because there's an explicit
    // UNKNOWN-marked nullability annotation:
    String qux(@Nonnull(when = When.UNKNOWN) String x); // fun baz(x: String!): String?
}

Типы в этом примере используются только при включенном строгом режиме; в противном случае остаются платформенные типы. См. разделы об аннотации @UnderMigration и настройке компилятора.

Также поддерживается nullability по умолчанию на уровне пакета:

// FILE: test/package-info.java
@NonNullApi // declaring all types in package 'test' as non-nullable by default
package test;

Аннотация @UnderMigration

Аннотация @UnderMigration (предоставляемая в отдельном артефакте kotlin-annotations-jvm) может использоваться разработчиками библиотек для определения статуса миграции квалификаторов типов nullability.

Значение статуса в @UnderMigration(status = ...) определяет, как компилятор обрабатывает некорректное использование аннотированных типов в Kotlin (например, использование значения типа с аннотацией @MyNullable в качестве non-null):

  • MigrationStatus.STRICT заставляет аннотацию работать как обычную аннотацию nullability: сообщать об ошибках при некорректном использовании и влиять на то, как аннотированные объявления представлены в Kotlin

  • MigrationStatus.WARN: о некорректном использовании сообщается предупреждениями компиляции, а не ошибками, но типы в аннотированных объявлениях остаются платформенными

  • MigrationStatus.IGNORE заставляет компилятор полностью игнорировать аннотацию nullability

Разработчик библиотеки может добавить статус @UnderMigration как к псевдонимам квалификаторов типов, так и к значениям по умолчанию для квалификаторов типов:

@Nonnull(when = When.ALWAYS)
@TypeQualifierDefault({ElementType.METHOD, ElementType.PARAMETER})
@UnderMigration(status = MigrationStatus.WARN)
public @interface NonNullApi {
}

// The types in the class are non-nullable, but only warnings are reported
// because `@NonNullApi` is annotated `@UnderMigration(status = MigrationStatus.WARN)`
@NonNullApi
public class Test {}

Статус миграции аннотации nullability не наследуется ее псевдонимами квалификаторов типов, но применяется к их использованию в значениях по умолчанию для квалификаторов типов.

Если в значении по умолчанию для квалификатора типа используется псевдоним квалификатора типа и оба они имеют статус @UnderMigration, применяется статус из значения по умолчанию для квалификатора типа.

Настройка компилятора

Проверки JSR-305 можно настроить, добавив флаг компилятора -Xjsr305 со следующими параметрами (или их комбинацией):

  • -Xjsr305={strict|warn|ignore} — задать поведение для аннотаций, отличных от @UnderMigration. Пользовательские квалификаторы nullability, особенно @TypeQualifierDefault, уже широко используются во многих известных библиотеках, и при обновлении до версии Kotlin с поддержкой JSR-305 пользователям может потребоваться плавный переход. Начиная с Kotlin 1.1.60 этот флаг влияет только на аннотации, отличные от @UnderMigration.

  • -Xjsr305=under-migration:{strict|warn|ignore} — переопределить поведение для аннотаций @UnderMigration. Пользователи могут по-разному оценивать статус миграции библиотек: они могут захотеть получать ошибки, хотя официальный статус миграции — WARN, или, наоборот, отложить выдачу ошибок для некоторых библиотек до завершения миграции.

  • -Xjsr305=@<fq.name>:{strict|warn|ignore} — переопределить поведение для одной аннотации, где <fq.name> — полное имя класса аннотации. Параметр можно указывать несколько раз для разных аннотаций. Это полезно для управления состоянием миграции отдельной библиотеки.

Значения strict, warn и ignore имеют тот же смысл, что и значения MigrationStatus, и только режим strict влияет на то, как аннотированные объявления представлены в Kotlin.

Примечание: встроенные аннотации JSR-305 @Nonnull, @Nullable и @CheckForNull всегда включены и влияют на типы аннотированных объявлений в Kotlin независимо от настройки компилятора с помощью флага -Xjsr305.

Например, добавление -Xjsr305=ignore -Xjsr305=under-migration:ignore -Xjsr305=@org.library.MyNullable:warn к аргументам компилятора заставляет компилятор выдавать предупреждения при некорректном использовании типов с аннотацией @org.library.MyNullable и игнорировать все остальные аннотации JSR-305.

По умолчанию используется поведение, совпадающее с -Xjsr305=warn. Значение strict следует считать экспериментальным (в будущем в него могут добавить дополнительные проверки).

Сопоставленные типы

Kotlin обрабатывает некоторые типы Java особым образом. Такие типы загружаются не из Java «как есть», а сопоставляются с соответствующими типами Kotlin. Это сопоставление имеет значение только во время компиляции, представление во время выполнения остается неизменным. Примитивные типы Java сопоставляются с соответствующими типами Kotlin (учитывая платформенные типы):

Тип Java

Тип Kotlin

byte

kotlin.Byte

short

kotlin.Short

int

kotlin.Int

long

kotlin.Long

char

kotlin.Char

float

kotlin.Float

double

kotlin.Double

boolean

kotlin.Boolean

Некоторые встроенные классы, не являющиеся примитивными, также сопоставляются:

Тип Java

Тип Kotlin

java.lang.Object

kotlin.Any!

java.lang.Cloneable

kotlin.Cloneable!

java.lang.Comparable

kotlin.Comparable!

java.lang.Enum

kotlin.Enum!

java.lang.annotation.Annotation

kotlin.Annotation!

java.lang.CharSequence

kotlin.CharSequence!

java.lang.String

kotlin.String!

java.lang.Number

kotlin.Number!

java.lang.Throwable

kotlin.Throwable!

Примитивные типы Java в упаковке сопоставляются с nullable-типами Kotlin:

Тип Java

Тип Kotlin

java.lang.Byte

kotlin.Byte?

java.lang.Short

kotlin.Short?

java.lang.Integer

kotlin.Int?

java.lang.Long

kotlin.Long?

java.lang.Character

kotlin.Char?

java.lang.Float

kotlin.Float?

java.lang.Double

kotlin.Double?

java.lang.Boolean

kotlin.Boolean?

Обратите внимание: примитивный тип в упаковке, используемый как параметр типа, сопоставляется с платформенным типом: например, List<java.lang.Integer> в Kotlin становится List<Int!>.

В Kotlin типы коллекций могут быть доступными только для чтения или изменяемыми, поэтому коллекции Java сопоставляются следующим образом (все типы Kotlin в этой таблице находятся в пакете kotlin.collections):

Тип Java

Тип Kotlin только для чтения

Изменяемый тип Kotlin

Загруженный платформенный тип

Iterator<T>

Iterator<T>

MutableIterator<T>

(Mutable)Iterator<T>!

Iterable<T>

Iterable<T>

MutableIterable<T>

(Mutable)Iterable<T>!

Collection<T>

Collection<T>

MutableCollection<T>

(Mutable)Collection<T>!

Set<T>

Set<T>

MutableSet<T>

(Mutable)Set<T>!

List<T>

List<T>

MutableList<T>

(Mutable)List<T>!

ListIterator<T>

ListIterator<T>

MutableListIterator<T>

(Mutable)ListIterator<T>!

Map<K, V>

Map<K, V>

MutableMap<K, V>

(Mutable)Map<K, V>!

Map.Entry<K, V>

Map.Entry<K, V>

MutableMap.MutableEntry<K,V>

(Mutable)Map.(Mutable)Entry<K, V>!

Массивы Java сопоставляются, как указано ниже:

Тип Java

Тип Kotlin

int[]

kotlin.IntArray!

String[]

kotlin.Array<(out) String!>!

Статические члены этих типов Java недоступны напрямую через объекты-компаньоны типов Kotlin. Чтобы вызвать их, используйте полные квалифицированные имена типов Java, например java.lang.Integer.toHexString(foo).

Обобщения Java в Kotlin

Обобщения Kotlin немного отличаются от обобщений Java (см. раздел Обобщения). При импорте типов Java в Kotlin выполняются следующие преобразования:

  • Подстановочные знаки Java преобразуются в проекции типов:

    • Foo<? extends Bar> становится Foo<out Bar!>!

    • Foo<? super Bar> становится Foo<in Bar!>!

  • Сырые типы Java преобразуются в проекции со звездочкой:

    • List становится List<*>!, то есть List<out Any?>!

Как и в Java, обобщения Kotlin не сохраняются во время выполнения: объекты не содержат информации о фактических аргументах типов, переданных их конструкторам. Например, ArrayList<Integer>() неотличим от ArrayList<Character>(). Поэтому невозможно выполнять проверки is с учетом обобщений. Kotlin разрешает проверки is только для обобщенных типов с проекцией со звездочкой:

if (a is List<Int>) // Error: cannot check if it is really a List of Ints
// but
if (a is List<*>) // OK: no guarantees about the contents of the list

Массивы Java

В отличие от Java, массивы в Kotlin инвариантны. Это означает, что Kotlin не позволит присвоить Array<String> переменной типа Array<Any>, что предотвращает возможную ошибку во время выполнения. Передавать массив подкласса в метод Kotlin в качестве массива суперкласса также запрещено, но для методов Java это разрешено благодаря платформенным типам вида Array<(out) String>!.

На платформе Java массивы используются с примитивными типами данных, чтобы избежать затрат на упаковку и распаковку. Поскольку Kotlin скрывает эти детали реализации, для взаимодействия с кодом Java требуется обходное решение. Для этого предусмотрены специализированные классы для каждого типа массива примитивов (IntArray, DoubleArray, CharArray и так далее). Они не связаны с классом Array и компилируются в примитивные массивы Java для обеспечения максимальной производительности.

Предположим, что в Java есть метод, принимающий массив индексов типа int:

public class JavaArrayExample {
    public void removeIndices(int[] indices) {
        // code here...
    }
}

Чтобы передать массив примитивных значений, в Kotlin можно сделать следующее:

val javaObj = JavaArrayExample()
val array = intArrayOf(0, 1, 2, 3)
javaObj.removeIndices(array)  // passes int[] to method

При компиляции в байт-код JVM компилятор оптимизирует доступ к массивам, исключая дополнительные накладные расходы:

val array = arrayOf(1, 2, 3, 4)
array[1] = array[1] * 2 // no actual calls to get() and set() generated
for (x in array) { // no iterator created
    print(x)
}

Даже при обращении по индексу никаких дополнительных накладных расходов не возникает:

for (i in array.indices) { // no iterator created
    array[i] += 2
}

Наконец, проверки in также не требуют дополнительных накладных расходов:

if (i in array.indices) { // same as (i >= 0 && i < array.size)
    print(array[i])
}

Переменное число аргументов в Java

В классах Java иногда используется объявление метода с переменным числом аргументов (varargs) для индексов:

public class JavaArrayExample {

    public void removeIndicesVarArg(int... indices) {
        // code here...
    }
}

В этом случае для передачи IntArray нужно использовать оператор распространения *:

val javaObj = JavaArrayExample()
val array = intArrayOf(0, 1, 2, 3)
javaObj.removeIndicesVarArg(*array)

Операторы

Поскольку в Java нет способа пометить методы, для которых уместен операторный синтаксис, Kotlin разрешает использовать любые методы Java с подходящими именем и сигнатурой в качестве перегрузок операторов и для других соглашений (invoke() и т. д.). Вызов методов Java с использованием инфиксного синтаксиса не разрешен.

Проверяемые исключения

В Kotlin все исключения являются непроверяемыми, то есть компилятор не требует перехватывать их. Поэтому при вызове метода Java, объявляющего проверяемое исключение, Kotlin не требует от вас никаких действий:

fun render(list: List<*>, to: Appendable) {
    for (item in list) {
        to.append(item.toString()) // Java would require us to catch IOException here
    }
}

Методы Object

При импорте типов Java в Kotlin все ссылки на тип java.lang.Object преобразуются в Any. Поскольку Any не зависит от платформы, в нем объявлены только toString(), hashCode() и equals(), поэтому для предоставления доступа к другим членам java.lang.Object Kotlin использует функции-расширения.

wait() и notify()

Методы wait() и notify() недоступны для ссылок типа Any. Обычно их использование не рекомендуется; предпочтительнее использовать java.util.concurrent.

Если вам необходимо вызвать эти методы, обратитесь к ним через объекты Java и подавите предупреждение PLATFORM_CLASS_MAPPED_TO_KOTLIN:

import java.util.LinkedList

class SimpleBlockingQueue<T>(private val capacity: Int) {
    private val queue = LinkedList<T>()

    // java.lang.Object is used specifically to access wait() and notify()
    // In Kotlin, the standard 'Any' type does not expose these methods.
    @Suppress("PLATFORM_CLASS_MAPPED_TO_KOTLIN")
    private val lock = Object()

    fun put(item: T) {
        synchronized(lock) {
            while (queue.size >= capacity) {
                lock.wait()
            }
            queue.add(item)
            println("Produced: $item")

            lock.notifyAll()
        }
    }

    fun take(): T {
        synchronized(lock) {
            while (queue.isEmpty()) {
                lock.wait()
            }
            val item = queue.removeFirst()
            println("Consumed: $item")

            lock.notifyAll()
            return item
        }
    }
}

Или выполните явное приведение к типу java.lang.Object и подавите предупреждение PLATFORM_CLASS_MAPPED_TO_KOTLIN:

@Suppress("PLATFORM_CLASS_MAPPED_TO_KOTLIN")
(foo as java.lang.Object).wait()

getClass()

Чтобы получить класс Java объекта, используйте свойство-расширение java для ссылки на класс:

val fooClass = foo::class.java

В приведенном выше коде используется связанная ссылка на класс. Также можно использовать свойство-расширение javaClass:

val fooClass = foo.javaClass

clone()

Чтобы переопределить clone(), ваш класс должен наследоваться от kotlin.Cloneable:

class Example : Cloneable {
    override fun clone(): Any { ... }
}

Не забудьте о книге «Эффективная Java», 3-е издание, пункт 13: переопределяйте clone с осторожностью.

finalize()

Чтобы переопределить finalize(), достаточно просто объявить его, не используя ключевое слово override:

class C {
    protected fun finalize() {
        // finalization logic
    }
}

Согласно правилам Java, finalize() не должен быть private.

Наследование от классов Java

Класс в Kotlin может иметь в качестве суперкласса не более одного класса Java (и любое количество интерфейсов Java).

Доступ к статическим членам

Статические члены классов Java образуют для этих классов «объекты-компаньоны». Передавать такой «объект-компаньон» как значение нельзя, но можно обращаться к его членам напрямую, например:

if (Character.isLetter(a)) { ... }

Чтобы получить доступ к статическим членам типа Java, сопоставленного с типом Kotlin, используйте полное квалифицированное имя типа Java: java.lang.Integer.bitCount(foo).

Рефлексия Java

Рефлексия Java работает с классами Kotlin и наоборот. Как упоминалось выше, для перехода к рефлексии Java можно использовать instance::class.java, ClassName::class.java или instance.javaClass через java.lang.Class. Не используйте для этого ClassName.javaClass, поскольку оно ссылается на класс объекта-компаньона ClassName, который совпадает с ClassName.Companion::class.java, а не с ClassName::class.java.

Для каждого примитивного типа существуют два разных класса Java, и Kotlin предоставляет способы получить оба. Например, Int::class.java вернет экземпляр класса, представляющий сам примитивный тип и соответствующий Integer.TYPE в Java. Чтобы получить класс соответствующего типа-обертки, используйте Int::class.javaObjectType, что эквивалентно Integer.class в Java.

К другим поддерживаемым случаям относятся получение метода-геттера или метода-сеттера Java либо резервного поля свойства Kotlin, получение KProperty для поля Java, а также получение метода или конструктора Java для KFunction и наоборот.

SAM-преобразования

Kotlin поддерживает SAM-преобразования как для Java-, так и для интерфейсов Kotlin. Поддержка Java означает, что функциональные литералы Kotlin могут автоматически преобразовываться в реализации интерфейсов Java с одним нестандартным методом, если типы параметров метода интерфейса совпадают с типами параметров функции Kotlin.

Это можно использовать для создания экземпляров SAM-интерфейсов:

val runnable = Runnable { println("This runs in a runnable") }

...и при вызове методов:

val executor = ThreadPoolExecutor()
// Java signature: void execute(Runnable command)
executor.execute { println("This runs in a thread pool") }

Если в классе Java есть несколько методов, принимающих функциональные интерфейсы, нужный метод можно выбрать с помощью функции-адаптера, преобразующей лямбда-выражение в конкретный SAM-тип. При необходимости такие функции-адаптеры также генерируются компилятором:

executor.execute(Runnable { println("This runs in a thread pool") })

SAM-преобразования работают только для интерфейсов, но не для абстрактных классов, даже если у тех также есть только один абстрактный метод.

Использование JNI с Kotlin

Чтобы объявить функцию, реализованную в машинном коде (C или C++), необходимо пометить ее модификатором external:

external fun foo(x: Int): Double

Остальная часть процедуры выполняется точно так же, как в Java.

Геттеры и сеттеры свойств также можно пометить как external:

var myProperty: String
    external get
    external set

Внутри это создаст две функции getMyProperty и setMyProperty, обе помеченные как external.

Использование объявлений, сгенерированных Lombok, в Kotlin

В коде Kotlin можно использовать объявления, сгенерированные Lombok для Java. Если вам нужно сгенерировать и использовать эти объявления в одном смешанном модуле Java/Kotlin, инструкции приведены на странице плагина компилятора Lombok. Если вы вызываете такие объявления из другого модуля, использовать этот плагин для компиляции того модуля не нужно.

12 августа 2026 г.
Сравнение с JavaВызов Kotlin из Java

© 2010–2026 JetBrains s.r.o. and Kotlin Programming Language contributors
Licensed under the Apache License, Version 2.0.
https://kotlinlang.org/docs/java-interop.html

Spec-Zone.ru

Настройки Оффлайн Что нового Помощь О нас
Spec-Zone .ru
спецификации, руководства, описания, API