Вызов 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 constant = 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.
Компилятор генерирует все члены DefaultImpls с аннотацией @Deprecated: вы не должны использовать эти члены в Java-коде, потому что компилятор генерирует их только для целей совместимости.
В случае наследования от интерфейса 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
Чтобы изменить имена сгенерированных методов доступа к свойствам без явной реализации методов getter и setter, вы можете использовать @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 нет исключений с проверкой. Поэтому, как правило, сигнатуры функций Kotlin на Java не объявляют выбрасываемые исключения. Таким образом, если у вас есть функция в 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