Использование функции по умолчанию реализации интерфейса в Kotlin
У меня есть интерфейс Kotlin с реализацией по умолчанию, например:
interface Foo {
fun bar(): String {
return "baz"
}
}
Это было бы хорошо, пока я не попытаюсь реализовать этот интерфейс из Java. Когда я это делаю, он говорит, что класс должен быть помечен как абстрактный или реализовать метод bar()
, Также, когда я пытаюсь реализовать метод, я не могу вызвать super.bar()
,
5 ответов
Пожалуйста, смотрите связанную проблему.
В комментариях есть рекомендация:
Напишите ваш интерфейс на Java (с методами по умолчанию), и классы Java и Kotlin правильно используют эти значения по умолчанию
Генерация истины default
Методы, вызываемые из Java - экспериментальная особенность Kotlin 1.2.40.
Вы должны аннотировать методы с помощью @JvmDefault
аннотация:
interface Foo {
@JvmDefault
fun bar(): String {
return "baz"
}
}
Эта функция по-прежнему отключена по умолчанию, вам нужно передать -Xenable-jvm-default
флаг компилятору, чтобы он работал. (Если вам нужно сделать это в Gradle, смотрите здесь).
Это действительно экспериментально, однако. Сообщение в блоге предупреждает, что и дизайн, и реализация могут измениться в будущем, и, по крайней мере, в моей IDE классы Java по-прежнему помечены ошибками из-за того, что эти методы не реализованы, несмотря на то, что компиляция и работает нормально.
Если вы знаете, что не будете переопределять функцию в каких-либо реализациях вашего интерфейса, вы можете использовать функции расширения как хороший обходной путь для этой проблемы. Просто поместите функцию расширения в тот же файл, что и интерфейс (и на верхнем уровне, чтобы другие файлы могли использовать ее).
Например, то, что вы делаете, может быть сделано следующим образом:
interface Foo {
// presumably other stuff
}
fun Foo.bar(): String {
return "baz"
}
См. Документы по функциям расширения для получения дополнительной информации о них.
Стоит отметить одну "ошибку":
Мы хотели бы подчеркнуть, что функции расширения отправляются статически, то есть они не являются виртуальными по типу получателя. Это означает, что вызываемая функция расширения определяется типом выражения, для которого вызывается функция, а не типом результата вычисления этого выражения во время выполнения.
Проще говоря, функции расширения не делают того, что вы ожидаете от обычного полиморфизма. Это означает, что функция по умолчанию не может быть переопределена, как обычная функция. Если вы попытаетесь переопределить его, вы получите странное поведение, потому что "переопределенная" версия будет вызываться всякий раз, когда вы явно имеете дело с подклассом, но версия расширения будет вызываться, когда вы имеете дело с интерфейсом в общем., Например:
interface MyInterface {
fun a()
}
fun MyInterface.b() {
println("MyInterface.b() default implementation")
}
class MyInterfaceImpl : MyInterface {
override fun a() {
println("MyInterfaceImpl.a()")
}
fun b() {
println("MyInterfaceImpl.b() \"overridden\" implementation")
}
}
fun main(args: Array<String>) {
val inst1: MyInterface = MyInterfaceImpl()
inst1.a()
inst1.b() // calls the "default" implementation
val inst2: MyInterfaceImpl = MyInterfaceImpl() // could also just do "val inst2 = MyInterfaceImpl()" (the type is inferred)
inst2.a()
inst2.b() // calls the "overridden" implementation
}
Начиная с Kotlin 1.4.0, вы можете использовать один из следующих флагов компилятора:
-
-Xjvm-default=all
-
-Xjvm-default=all-compatibility
(для бинарной совместимости со старым кодом Kotlin)
Это позволит компилировать метод JVM по умолчанию для всех интерфейсов.
Если вы хотите узнать, как установить эти флаги в вашей IDE или проекте Maven/Gradle, ознакомьтесь с документацией по параметрам компилятора .
Прогресс в этом отслеживается в выпуске KT-4779, который также включает полезную сводку текущего состояния.
@JvmDefault
аннотация и старше
-Xjvm-default=enable
а также
-Xjvm-default=compatibility
флаги компилятора больше не должны использоваться.
В отличие от более ранней версии Java8, Kotlin может иметь реализацию по умолчанию в интерфейсе.
Когда вы реализуете Foo
интерфейс в класс Java. Котлин скрывает эти реализации метода интерфейса. Как указано здесь.
Массивы используются с примитивными типами данных на платформе Java, чтобы избежать затрат на операции упаковки / распаковки. Поскольку Kotlin скрывает эти детали реализации, для взаимодействия с Java-кодом требуется обходной путь
Это характерно для массивов в приведенной выше ссылке, но также относится ко всем классам (возможно, для поддержки более ранней версии Java8).
РЕДАКТИРОВАТЬ
Выше объяснение основано на мнении.
Одна вещь, с которой я столкнулся, и это главная причина.
Двоичные файлы Kotlin были скомпилированы с байт-кодом Java версии 1.8 без использования методов по умолчанию в интерфейсах. И они сталкиваются с критической проблемой, решая ее.