Вызов Kotlin из Java
Код Kotlin легко вызывается из Java. Например, экземпляры класса Kotlin можно без проблем создавать и использовать в методах Java. Однако существуют некоторые различия между Java и Kotlin, которые требуют внимания при интеграции кода Kotlin в Java. На этой странице мы опишем способы настройки взаимодействия вашего кода Kotlin с его клиентами Java.
Свойства
Свойство Kotlin компилируется в следующие элементы Java:
метод-геттер, имя которого рассчитывается путем добавления префикса
getметод-сеттер, имя которого рассчитывается путем добавления префикса
set(только дляvarсвойств)приватное поле с тем же именем, что и имя свойства (только для свойств с вспомогательными полями)
Например, var firstName: String компилируется в следующие объявления Java:
private String firstName;
public String getFirstName() {
return firstName;
}
public void setFirstName(String firstName) {
this.firstName = firstName;
}
Если имя свойства начинается с is, используется другое правило сопоставления имен: имя геттера будет таким же, как имя свойства, а имя сеттера будет получено путем замены is на set. Например, для свойства isOpen, геттер будет называться isOpen(), а сеттер — setOpen(). Это правило применяется для свойств любого типа, а не только Boolean.
Функции уровня пакета
Все функции и свойства, объявленные в файле app.kt внутри пакета org.example, включая расширяющие функции, компилируются в статические методы класса Java с именем org.example.AppKt.
// app.kt
package org.example
class Util
fun getTime() { /*...*/ }
// Java new org.example.Util(); org.example.AppKt.getTime();
Чтобы установить пользовательское имя для сгенерированного класса Java, используйте аннотацию @JvmName.
@file:JvmName("DemoUtils")
package org.example
class Util
fun getTime() { /*...*/ }
// Java new org.example.Util(); org.example.DemoUtils.getTime();
Наличие нескольких файлов с одинаковым именем сгенерированного класса Java (один и тот же пакет и то же самое имя или аннотация @JvmName) обычно является ошибкой. Однако компилятор может сгенерировать единственный класс-фасад Java, который имеет указанное имя и содержит все объявления из всех файлов, имеющих это имя. Чтобы включить генерацию такого фасада, используйте аннотацию @JvmMultifileClass во всех таких файлах.
// oldutils.kt
@file:JvmName("Utils")
@file:JvmMultifileClass
package org.example
fun getTime() { /*...*/ }
// newutils.kt
@file:JvmName("Utils")
@file:JvmMultifileClass
package org.example
fun getDate() { /*...*/ }
// Java org.example.Utils.getTime(); org.example.Utils.getDate();
Поля экземпляров
Если вам нужно экспонировать свойство Kotlin в виде поля в Java, добавьте к нему аннотацию @JvmField. Поле будет иметь ту же видимость, что и базовое свойство. Вы можете добавить аннотацию @JvmField если оно:
имеет вспомогательное поле
не является приватным
не имеет модификаторов
open,overrideилиconstне является делегированным свойством
class User(id: String) {
@JvmField val ID = id
}
// Java
class JavaClient {
public String getID(User user) {
return user.ID;
}
}
Отложенно инициализируемые свойства также экспонируются как поля. Видимость поля будет такой же, как и видимость lateinit сеттера свойства.
Статические поля
Свойства Kotlin, объявленные в именованных объектах или компаньон-объектах, будут иметь статические вспомогательные поля либо в этом именованном объекте, либо в классе, содержащем компаньон-объект.
Обычно эти поля являются приватными, но их можно экспонировать одним из следующих способов:
@JvmFieldаннотациямодификатор
lateinitмодификатор
const
Аннотирование такого свойства с помощью @JvmField делает его статическим полем с такой же видимостью, как и у самого свойства.
class Key(val value: Int) {
companion object {
@JvmField
val COMPARATOR: Comparator<Key> = compareBy<Key> { it.value }
}
}
// Java Key.COMPARATOR.compare(key1, key2); // public static final field in Key class
Отложенно инициализируемое свойство в объекте или компаньон-объекте имеет статическое вспомогательное поле с такой же видимостью, как у сеттера свойства.
object Singleton {
lateinit var provider: Provider
}
// Java Singleton.provider = new Provider(); // public static non-final field in Singleton class
Свойства, объявленные как const (в классах, а также на верхнем уровне), преобразуются в статические поля в Java:
// file example.kt
object Obj {
const val CONST = 1
}
class C {
companion object {
const val VERSION = 9
}
}
const val MAX = 239
В Java:
int const = Obj.CONST; int max = ExampleKt.MAX; int version = C.VERSION;
Статические методы
Как упоминалось выше, функции уровня пакета Kotlin представляются как статические методы. Kotlin также может генерировать статические методы для функций, определенных в именованных объектах или компаньон-объектах, если вы аннотируете эти функции как @JvmStatic. Если вы используете эту аннотацию, компилятор сгенерирует как статический метод в окружающем классе объекта, так и метод экземпляра в самом объекте. Например:
class C {
companion object {
@JvmStatic fun callStatic() {}
fun callNonStatic() {}
}
}
Теперь callStatic() является статическим в Java, в то время как callNonStatic() — нет:
C.callStatic(); // works fine C.callNonStatic(); // error: not a static method C.Companion.callStatic(); // instance method remains C.Companion.callNonStatic(); // the only way it works
То же самое для именованных объектов:
object Obj {
@JvmStatic fun callStatic() {}
fun callNonStatic() {}
}
В Java:
Obj.callStatic(); // works fine Obj.callNonStatic(); // error Obj.INSTANCE.callNonStatic(); // works, a call through the singleton instance Obj.INSTANCE.callStatic(); // works too
Начиная с Kotlin 1.3, @JvmStatic применяется к функциям, определенным в компаньон-объектах интерфейсов, а такие функции компилируются в статические методы в интерфейсах. Обратите внимание, что статические методы в интерфейсах были введены в Java 1.8, поэтому убедитесь, что вы используете соответствующие целевые версии.
interface ChatBot {
companion object {
@JvmStatic fun greet(username: String) {
println("Hello, $username")
}
}
}
@JvmStatic аннотация также может быть применена к свойству объекта или компаньон-объекта, делая его методы-геттер и -сеттер статическими членами этого объекта или класса, содержащего компаньон-объект.
Методы по умолчанию в интерфейсах
Начиная с JDK 1.8, интерфейсы в Java могут содержать методы по умолчанию. Чтобы сделать все неабстрактные члены Kotlin-интерфейсов методами по умолчанию для Java-классов, реализующих их, скомпилируйте код Kotlin с опцией компилятора -Xjvm-default=all.
Вот пример Kotlin-интерфейса с методом по умолчанию:
// compile with -Xjvm-default=all
interface Robot {
fun move() { println("~walking~") } // will be default in the Java interface
fun speak(): Unit
}
Реализация по умолчанию доступна для Java-классов, реализующих интерфейс.
//Java implementation
public class C3PO implements Robot {
// move() implementation from Robot is available implicitly
@Override
public void speak() {
System.out.println("I beg your pardon, sir");
}
}
C3PO c3po = new C3PO(); c3po.move(); // default implementation from the Robot interface c3po.speak();
Реализации интерфейса могут переопределять методы по умолчанию.
//Java
public class BB8 implements Robot {
//own implementation of the default method
@Override
public void move() {
System.out.println("~rolling~");
}
@Override
public void speak() {
System.out.println("Beep-beep");
}
}
Режимы совместимости для методов по умолчанию
Если есть клиенты, которые используют ваши Kotlin-интерфейсы, скомпилированные без опции -Xjvm-default=all, то они могут быть бинарно несовместимы с кодом, скомпилированным с этой опцией. Чтобы избежать разрыва совместимости с такими клиентами, используйте режим -Xjvm-default=all и отметьте интерфейсы аннотацией @JvmDefaultWithCompatibility. Это позволяет вам добавить эту аннотацию ко всем интерфейсам в общедоступном API один раз, и вам не нужно будет использовать какие-либо аннотации для нового непубличного кода.
Узнайте больше о режимах совместимости:
disable
Поведение по умолчанию. Не генерировать методы JVM по умолчанию и запретить использование аннотации @JvmDefault.
all
Генерировать методы JVM по умолчанию для всех объявлений интерфейсов с телами в модуле. Не генерировать DefaultImplsзаглушки для объявлений интерфейсов с телами, которые генерируются по умолчанию в режиме disable.
Если интерфейс наследует метод с телом из интерфейса, скомпилированного в режиме disable , и не переопределяет его, то для него будет сгенерирована DefaultImpls заглушка.
Нарушает бинарную совместимость, если какой-то клиентский код полагается на присутствие DefaultImpls классов.
all-compatibility
В дополнение к режиму all генерировать заглушки совместимости в классах DefaultImpls. Заглушки совместимости могут быть полезны для авторов библиотек и исполняемых файлов для сохранения обратной бинарной совместимости для существующих клиентов, скомпилированных против предыдущих версий библиотек. all и all-compatibility режимы изменяют поверхность ABI библиотек, которую будут использовать клиенты после повторной компиляции библиотеки. В этом смысле клиенты могут быть несовместимы с предыдущими версиями библиотек. Обычно это означает, что вам нужна правильная версия библиотеки, например, увеличение основной версии в SemVer.
В случае наследования от Kotlin-интерфейса, скомпилированного в режимах all или all-compatibility , DefaultImpls заглушки совместимости вызывают метод по умолчанию интерфейса с семантикой стандартного разрешения времени выполнения JVM.
Выполнять дополнительные проверки совместимости для классов, наследующих обобщенные интерфейсы, где в некоторых случаях в режиме disable был сгенерирован дополнительный неявный метод со специализированными сигнатурами: в отличие от режима disable , компилятор будет сообщать об ошибке, если вы не явно переопределите такой метод и не аннотируете класс с @JvmDefaultWithoutCompatibility (см. этот выпуск YouTrack для получения дополнительной информации).
Видимость
Модификаторы видимости Kotlin отображаются в Java следующим образом:
privateчлены компилируются вprivateчленыprivateобъявления верхнего уровня компилируются в объявления пакетаprotectedостаетсяprotected(обратите внимание, что Java позволяет обращаться к защищенным членам из других классов в том же пакете, а Kotlin — нет, поэтому Java-классы будут иметь более широкий доступ к коду)internalобъявления становятсяpublicв Java. Члены классовinternalпроходят искажение имен, чтобы затруднить случайное использование их из Java и для разрешения перегрузки для членов с одинаковой сигнатурой, которые не видят друг друга в соответствии с правилами Kotlinpublicостаетсяpublic
KClass
Иногда вам нужно вызвать Kotlin-метод с параметром типа KClass. Нет автоматического преобразования из Class в KClass, поэтому вам нужно сделать это вручную, вызвав эквивалент свойства расширения Class<T>.kotlin:
kotlin.jvm.JvmClassMappingKt.getKotlinClass(MainView.class)
Обработка конфликтов сигнатур с @JvmName
Иногда у нас есть именованная функция в Kotlin, для которой нам нужно другое имя JVM в байткоде. Наиболее яркий пример возникает из-за стирания типов:
fun List<String>.filterValid(): List<String> fun List<Int>.filterValid(): List<Int>
Эти две функции не могут быть определены рядом, потому что их JVM-сигнатуры одинаковы: filterValid(Ljava/util/List;)Ljava/util/List;. Если мы действительно хотим, чтобы они имели одинаковое имя в Kotlin, мы можем аннотировать одну (или обе) из них с @JvmName и указать другое имя в качестве аргумента:
fun List<String>.filterValid(): List<String>
@JvmName("filterValidInt")
fun List<Int>.filterValid(): List<Int>
Из Kotlin они будут доступны под одним именем filterValid, но из Java — filterValid и filterValidInt.
Та же уловка применима, когда нам нужно иметь свойство x наряду с функцией getX():
val x: Int
@JvmName("getX_prop")
get() = 15
fun getX() = 10
Чтобы изменить имена сгенерированных методов доступа к свойствам без явного реализации геттеров и сеттеров, вы можете использовать @get:JvmName и @set:JvmName:
@get:JvmName("x")
@set:JvmName("changeX")
var x: Int = 23
Генерация перегрузок
Обычно, если вы пишете Kotlin-функцию со значениями параметров по умолчанию, она будет видна в Java только как полная сигнатура со всеми параметрами. Если вы хотите экспонировать несколько перегрузок вызывающим сторонам Java, вы можете использовать аннотацию @JvmOverloads.
Аннотация также работает для конструкторов, статических методов и т. д. Она не может быть использована для абстрактных методов, включая методы, определенные в интерфейсах.
class Circle @JvmOverloads constructor(centerX: Int, centerY: Int, radius: Double = 1.0) {
@JvmOverloads fun draw(label: String, lineWidth: Int = 1, color: String = "red") { /*...*/ }
}
Для каждого параметра со значением по умолчанию это сгенерирует одну дополнительную перегрузку, которая имеет этот параметр и все параметры справа от него в списке параметров удалены. В этом примере будут сгенерированы следующие:
// Constructors:
Circle(int centerX, int centerY, double radius)
Circle(int centerX, int centerY)
// Methods
void draw(String label, int lineWidth, String color) { }
void draw(String label, int lineWidth) { }
void draw(String label) { }
Обратите внимание, что, как описано в вторичных конструкторах, если у класса есть значения по умолчанию для всех параметров конструктора, будет сгенерирован общедоступный конструктор без аргументов. Это работает даже если аннотация @JvmOverloads не указана.
Исключения с проверкой типов
В Kotlin нет исключений с проверкой типов. Поэтому подписи Java-функций Kotlin обычно не объявляют выбрасываемые исключения. Таким образом, если у вас есть функция в Kotlin, подобная этой:
// example.kt
package demo
fun writeToFile() {
/*...*/
throw IOException()
}
И вы хотите вызвать её из Java и перехватить исключение:
// Java
try {
demo.Example.writeToFile();
} catch (IOException e) {
// error: writeToFile() does not declare IOException in the throws list
// ...
}
Вы получите сообщение об ошибке от компилятора Java, потому что writeToFile() не объявляет IOException. Чтобы обойти эту проблему, используйте аннотацию @Throws в Kotlin:
@Throws(IOException::class)
fun writeToFile() {
/*...*/
throw IOException()
}
Безопасность от значений null
При вызове функций Kotlin из Java никто не запрещает нам передавать null в качестве параметра, не допускающего значение null. Именно поэтому Kotlin генерирует проверки на время выполнения для всех публичных функций, ожидающих не null. Таким образом, мы получаем NullPointerException в коде Java сразу.
Генерики с вариантами
Когда классы Kotlin используют вариацию на месте объявления, существует два варианта, как их использование будет отображаться в коде Java. Например, представьте, что у вас есть следующий класс и две функции, которые его используют:
class Box<out T>(val value: T) interface Base class Derived : Base fun boxDerived(value: Derived): Box<Derived> = Box(value) fun unboxBase(box: Box<Base>): Base = box.value
Примитивный способ перевода этих функций на Java будет следующим:
Box<Derived> boxDerived(Derived value) { ... }
Base unboxBase(Box<Base> box) { ... }
Проблема в том, что в Kotlin вы можете написать unboxBase(boxDerived(Derived())), но в Java это будет невозможно, потому что в Java класс Box является инвариантным относительно своего параметра T, и, следовательно, Box<Derived> не является подтипом Box<Base>. Чтобы это работало в Java, вам нужно было бы определить unboxBase следующим образом:
Base unboxBase(Box<? extends Base> box) { ... }
Это объявление использует типы-уайлдкарды Java (? extends Base) для имитации вариации на месте объявления с помощью вариации на месте использования, потому что в Java этого больше нет.
Чтобы API Kotlin работал в Java, компилятор генерирует Box<Super> как Box<? extends Super> для ковариантно определённых Box (или Foo<? super Bar> для контравариантно определённых Foo). При появлении в качестве параметра. Если это значение возвращаемого типа, уайлдкарды не генерируются, так как в противном случае клиенты Java должны будут иметь дело с ними (и это противоречит общепринятому стилю программирования Java). Поэтому функции из нашего примера фактически переводятся следующим образом:
// return type - no wildcards
Box<Derived> boxDerived(Derived value) { ... }
// parameter - wildcards
Base unboxBase(Box<? extends Base> box) { ... }
Если вам нужны уайлдкарды там, где они не генерируются по умолчанию, используйте аннотацию @JvmWildcard:
fun boxDerived(value: Derived): Box<@JvmWildcard Derived> = Box(value)
// is translated to
// Box<? extends Derived> boxDerived(Derived value) { ... }
В противном случае, если вам не нужны уайлдкарды там, где они генерируются, используйте @JvmSuppressWildcards:
fun unboxBase(box: Box<@JvmSuppressWildcards Base>): Base = box.value
// is translated to
// Base unboxBase(Box<Base> box) { ... }
Перевод типа Nothing
Тип Nothing является специальным, так как в Java у него нет аналога. Действительно, каждый тип ссылки Java, включая java.lang.Void, принимает null в качестве значения, а Nothing не принимает даже это. Таким образом, этот тип не может быть точно представлен в мире Java. Вот почему Kotlin генерирует сырой тип, когда используется аргумент типа Nothing:
fun emptyList(): List<Nothing> = listOf()
// is translated to
// List emptyList() { ... }
© 2010–2022 JetBrains s.r.o. and Kotlin Programming Language contributors
Licensed under the Apache License, Version 2.0.
https://kotlinlang.org/docs/java-to-kotlin-interop.html