Привет, Хабр!
Сегодня мы затронем важнейшую тему: интероперабельность 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-функции, нельзя аннотировать @JvmFieldJvmStatic
- Что она делает? Если она используется с функцией, то указывает, что из этого элемента должен быть сгенерирован дополнительный статический метод. Если она используется со свойством, то будут генерироваться дополнительные статические методы-геттеры и методы-сеттеры.
- Наиболее распространенный практический случай: предоставление членов (функций, свойств) из объекта-компаньона.
Как это работает?
Допустим, вы определяете функцию в
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.
 
           
 
First_Spectr
Небольшая опечатка, должен быть JvmStatic.
ph_piter Автор
Спасибо, поправили