Привет, Хабр!
Сегодня мы затронем важнейшую тему: интероперабельность Java и Kotlin. Авторы предлагаемой публикации разумно предполагают, что переписать на Kotlin базу кода, сделанную на Java, маловозможно. Поэтому правильнее обеспечить взаимодействие кода на Java и Kotlin. Читайте, как это можно сделать при помощи аннотаций.
Думаю, вы открыли эту статью по следующим причинам:
Почему?
Если Kotlin такой классный, почему бы не использовать его везде и всегда? Вот, навскидку, пара сценариев, в которых это невозможно:
Здесь мы рассмотрим несколько аннотаций, обеспечивающих интероперабельность между Java и Kotlin!
Аннотации Java
JvmField
Как это работает?
Допустим, вы определяете поле внутри
Если вы попытаетесь вызвать эту функцию из Java, то придется написать:
Очень много кода для простого поля! Чтобы сделать код чище, давайте уберем лишнее, добавив аннотацию.
Теперь наш код на Java будет выглядеть так:
Того же самого можно достичь и при помощи модификатора, однако, такой модификатор работает лишь с примитивными типами или строками.
Когда эту аннотацию нельзя использовать?
Свойства
JvmStatic
Как это работает?
Допустим, вы определяете функцию в
Если попытаетесь вызвать эту функцию из Java, то придется написать:
Нам приходится обращаться к объекту
Теперь, вызывая эту функцию из Java, мы должны будем написать всего лишь:
Так гораздо лучше, не правда ли? Ситуация такова, как если бы функция была исходно написана на Java как статический метод.
Также аннотации можно применять и к полям:
Вызывая этот код из Java, можно написать:
Обратите внимание:
А поскольку поле это
Если у нас внутри нашего объекта есть константа, то ее мы тоже можем объявить как статическую:
Однако, в данном случае использование аннотации – не лучшая идея, поскольку вызов на инвокацию выглядел бы так:
В таком случае используйте модификатор const или JvmField, как было объяснено выше.
Когда ее нельзя использовать?
Член нельзя аннотировать JvmStatic, когда он сопровождается модификатором
В такой ситуации код не скомпилируется:
JvmOverloads
Как это работает?
Если у вас есть класс с конструктором (или любой другой функцией) с параметрами, заданными по умолчанию…
… то вы сможете вызывать такую функцию из Kotlin различными способами:
Однако, если вы попытаетесь вызвать конструктор из Java, у вас будет всего два варианта: 1) передать все параметры или 2) только в случае, когда у ВСЕХ ваших параметров есть значения по умолчанию, можно не передавать вообще никаких параметров.
Если мы хотим создать перегрузки, то можем воспользоваться аннотацией
Теперь при использовании Java у нас появляется множество возможностей:
Однако, в Kotlin в данном случае вариантов не так много. Например, мы не сможем передать только фамилию или только возраст.
Аннотация
file:JvmName
Как она работает?
В Kotlin, где функции являются привилегированными элементами, можно писать функции, существующие вне класса. Например, если вы создаете новый файл Kotlin и пишете следующий код, то он скомпилируется без проблем:
Можно вызвать этот код из Java:
Обратите внимание: хотя файл и называется Utils, при вызове используется имя
Обратите внимание, как используется префикс
То также можно аннотировать функции:
При вызове этого кода из Kotlin, мы все равно будем пользоваться оригинальным именем (
Эта возможность не кажется особенно полезной, однако, с ее помощью можно разрешать конфликты сигнатур. Этот сценарий отлично разобран в официальной документации.
Здесь можно работать и с методами для доступа к свойствам:
Смотрите, как будет выглядеть этот вызов в Java с аннотацией и без нее:
Того же самого можно достичь при помощи префикса
Надеюсь, вам пригодился этот обзор аннотаций, помогающий писать на Kotlin код, удобный для использования с Java.
Сегодня мы затронем важнейшую тему: интероперабельность Java и Kotlin. Авторы предлагаемой публикации разумно предполагают, что переписать на Kotlin базу кода, сделанную на Java, маловозможно. Поэтому правильнее обеспечить взаимодействие кода на Java и Kotlin. Читайте, как это можно сделать при помощи аннотаций.
Думаю, вы открыли эту статью по следующим причинам:
- Наконец-то решили попробовать Kotlin.
- Он вам понравился, что, впрочем, неудивительно.
- Решили использовать Kotlin повсюду
- Столкнулись с суровой реальностью: от Java совсем отказаться не получается, как минимум, малой кровью.
Почему?
Если Kotlin такой классный, почему бы не использовать его везде и всегда? Вот, навскидку, пара сценариев, в которых это невозможно:
- Когда вы пытаетесь медленно перенести всю вашу базу кода на Kotlin, вы заметите, что попадаются такие файлы, к которым попросту страшно применить команду
Convert Java to Kotlin file
. Если у вас есть время на рефакторинг – займитесь им! Однако, в реальном проекте время на рефакторинг найдется не всегда. - Ваш код будут использовать программисты, работающие как с Java, так и с Kotlin. Вы не можете (или не должны) вынуждать их всех использовать конкретный язык, особенно если поддержка обоих языков не потребует от вас больших усилий (естественно, я говорю об аннотациях).
Здесь мы рассмотрим несколько аннотаций, обеспечивающих интероперабельность между Java и Kotlin!
Аннотации Java
JvmField
- Что она делает? Приказывает компилятору Kotlin не генерировать геттеры и сеттеры для данного свойства и предоставить его как поле.
- Наиболее распространенный практический случай: предоставить поля объекта-компаньона.
Как это работает?
Допустим, вы определяете поле внутри
object / companion object
в Kotlin:object Constants {
val PERMISSIONS = listOf("Internet", "Location")
}
Если вы попытаетесь вызвать эту функцию из Java, то придется написать:
Utils.INSTANCE.getPERMISSIONS()
Очень много кода для простого поля! Чтобы сделать код чище, давайте уберем лишнее, добавив аннотацию.
object Constants {
@JvmField
val PERMISSIONS = listOf("Internet", "Location")
}
Теперь наш код на Java будет выглядеть так:
Utils.PERMISSIONS;
Того же самого можно достичь и при помощи модификатора, однако, такой модификатор работает лишь с примитивными типами или строками.
//Kotin
object Constants {
const val KEY = "test"
}
//Java
String key = Constant.KEY;
Когда эту аннотацию нельзя использовать?
Свойства
const
, помеченные как and-функции, нельзя аннотировать @JvmField
JvmStatic
- Что она делает? Если она используется с функцией, то указывает, что из этого элемента должен быть сгенерирован дополнительный статический метод. Если она используется со свойством, то будут генерироваться дополнительные статические методы-геттеры и методы-сеттеры.
- Наиболее распространенный практический случай: предоставление членов (функций, свойств) из объекта-компаньона.
Как это работает?
Допустим, вы определяете функцию в
object
в Kotlin:object Utils {
fun doSomething(){ ... }
}
Если попытаетесь вызвать эту функцию из Java, то придется написать:
Utils.INSTANCE.doSomething()
Нам приходится обращаться к объекту
INSTANCE
всякий раз, когда мы хотим вызвать эту функцию. Чтобы сделать код чище, давайте лучше воспользуемся аннотацией @JvmStatic
.object Utils {
@JvmStatic
fun doSomething(){ ... }
}
Теперь, вызывая эту функцию из Java, мы должны будем написать всего лишь:
Utils.doSomething();
Так гораздо лучше, не правда ли? Ситуация такова, как если бы функция была исходно написана на Java как статический метод.
Также аннотации можно применять и к полям:
object Utils {
@JvmStatic
var values = listOf("Test 1", "Test 2")
}
Вызывая этот код из Java, можно написать:
Utils.getValues();
Обратите внимание:
JvmField
предоставляет член как поле, но с JvmStatic
мы предоставляем функцию get
.А поскольку поле это
var
, также генерируется метод set
:Utils.setValues(...);
Если у нас внутри нашего объекта есть константа, то ее мы тоже можем объявить как статическую:
object Utils {
@JvmStatic
val KEY = "test"
}
Однако, в данном случае использование аннотации – не лучшая идея, поскольку вызов на инвокацию выглядел бы так:
public void foo(){
String key = Utils.getKEY();
}
В таком случае используйте модификатор const или JvmField, как было объяснено выше.
Когда ее нельзя использовать?
Член нельзя аннотировать JvmStatic, когда он сопровождается модификатором
open
, override
или const
.В такой ситуации код не скомпилируется:
JvmOverloads
- Что она делает? Приказывает компилятору Kotlin сгенерировать перегрузки для данной функции, которая заменяет значения параметров, заданные по умолчанию.
- Что такое “перегрузка”? В Kotlin у вашей функции могут быть параметры по умолчанию, благодаря чему можно вызывать одну и ту же функцию различными способами. Чтобы достичь того же в Java, пришлось бы вручную определять каждую отдельную вариацию этой функции. Каждая из таких автоматически сгенерированных вариаций называется «перегрузкой». Наиболеее распространенный вариант использования: перегрузка конструкторов классов. Да, такой прием работает с любой функцией, у которой есть параметры по умолчанию.
Как это работает?
Если у вас есть класс с конструктором (или любой другой функцией) с параметрами, заданными по умолчанию…
class User constructor (
val name: String = "Test",
val lastName: String = "Testy", val age: Int = 0
)
… то вы сможете вызывать такую функцию из Kotlin различными способами:
val user1 = User()
val user2 = User(name = "Bruno")
val user3 = User(name = "Bruno", lastName = "Aybar")
val user4 = User(name = "Bruno", lastName = "Aybar", age = 21)
val user5 = User(lastName = "Aybar")
val user6 = User(lastName = "Aybar", age = 21) val user7 = User(age = 21)
val user8 = User(age = 21, name = "Bruno")
...
Однако, если вы попытаетесь вызвать конструктор из Java, у вас будет всего два варианта: 1) передать все параметры или 2) только в случае, когда у ВСЕХ ваших параметров есть значения по умолчанию, можно не передавать вообще никаких параметров.
Если мы хотим создать перегрузки, то можем воспользоваться аннотацией
JvmOverloads
:class User @JvmOverloads constructor ( val name: String = "Test",
val lastName: String = "Testy", val age: Int = 0
)
Теперь при использовании Java у нас появляется множество возможностей:
Однако, в Kotlin в данном случае вариантов не так много. Например, мы не сможем передать только фамилию или только возраст.
Аннотация
JvmOverloads
сгенерирует лишь столько перегрузок, сколько есть у функции параметров, заданных по умолчанию.- Если у вас есть функция, то ее можно пометить как
JvmOverload
. Можно даже скомбинировать ее с другими аннотациями, например, сJvmStatic
. - Когда ее не следует использовать? Эта аннотация бесполезна, если у функции нет параметров, заданных по умолчанию.
file:JvmName
- Что она делает? Указывает имя для класса или метода Java, генерируемого из данного элемента.
- Наиболее распространенный случай: дать более красивое имя файлу Kotlin. Однако, эта аннотация применима не только с файлами, но и с функциями, а также с методами для доступа к свойствам (геттерами и сеттерами).
Как она работает?
В Kotlin, где функции являются привилегированными элементами, можно писать функции, существующие вне класса. Например, если вы создаете новый файл Kotlin и пишете следующий код, то он скомпилируется без проблем:
//file name: Utils.kt
fun doSomething() { ... }
Можно вызвать этот код из Java:
UtilsKt.doSomething();
Обратите внимание: хотя файл и называется Utils, при вызове используется имя
UtilsKt
, а это не идеально. Чтобы это исправить, давайте добавим сверху файла аннотацию JvmName
.// имя файла: Utils.kt
@file:JvmName("Utils")
fun doSomething() { ... }
Обратите внимание, как используется префикс
file:
. Вероятно, вы уже догадались: он указывает, что используемая нами аннотация применяется на уровне файлов. Если вызвать следующий код из Java:Utils.doSomething();
То также можно аннотировать функции:
// имя файла: Utils.kt @file:JvmName("Utils")
@JvmName("doSomethingElse")
fun doSomething() { ... }
При вызове этого кода из Kotlin, мы все равно будем пользоваться оригинальным именем (
doSomething
), но в Java мы используем имя, указанное в аннотации://Java Utils.doSomethingElse();
//Kotlin Utils.doSomething()
Эта возможность не кажется особенно полезной, однако, с ее помощью можно разрешать конфликты сигнатур. Этот сценарий отлично разобран в официальной документации.
Здесь можно работать и с методами для доступа к свойствам:
class User {
val likesKotlin: Boolean = true
@JvmName("likesKotlin") get() = field
}
Смотрите, как будет выглядеть этот вызов в Java с аннотацией и без нее:
// Без аннотации
new User().getLikesKotlin()
// С аннотацией
new User().likesKotlin()
Того же самого можно достичь при помощи префикса
get
.class User {
@get:JvmName("likesKotlin")
val likesKotlin = true
}
- В каких случаях можно использовать такую возможность? С файлами, функциями, методами для доступа к свойствам. Однако, обязательно ставьте нужные префиксы в случае необходимости.
- Когда ею не следует пользоваться? Если произвольно задать функции альтернативное имя, то можно устроить большую путаницу. Пользуйтесь этой аннотацией осторожно, а если применяете – то применяйте согласованно.
Надеюсь, вам пригодился этот обзор аннотаций, помогающий писать на Kotlin код, удобный для использования с Java.