Проблема совместимости Kotlin с реализацией интерфейсов

Я работаю над переносом веб-проекта с Java на Kotlin. В ходе этого процесса я столкнулся с проблемой компиляции, которая может создать проблемы в будущем, особенно если существующие проекты должны повторно использовать компоненты Kotlin.

Проблему я локализовал и воспроизвел простым способом:

Версия Котлина: 1.9.23

Это EntityId, который является классом встроенных значений.

@JvmInline
value class EntityId(val id: UUID)

и интерфейс Kotlin, содержащий абстрактную функцию с EntityId

interface KotlinInterface<T> {

    fun findById(id: EntityId): T
}

Я пытаюсь реализовать этот интерфейс в Java (поскольку Java не поддерживает классы значений Kotlin, компилятор ожидает тип UUID вместо EntityId):

public class JavaImpl implements KotlinInterface<Entity> {

    @Override
    public Entity findById(@NotNull UUID id) {
        return new Entity(id, "Some name");
    }
}

IDE не обнаруживает никаких проблем, и об ошибках не сообщается.

Я пытаюсь скомпилировать проект с помощью Maven и получаю следующее:

[INFO] ------------------------------------------------------------------------
[INFO] BUILD FAILURE
[INFO] ------------------------------------------------------------------------
[INFO] Total time:  22.602 s
[INFO] Finished at: 2024-07-06T12:48:51+04:00
[INFO] ------------------------------------------------------------------------
[ERROR] Failed to execute goal org.apache.maven.plugins:maven-compiler-plugin:3.11.0:compile (compile) on project kotlin-interpolation: Compilation failure: Compilation failure: 
[ERROR] .../kotlin-interpolation/src/main/kotlin/com/example/JavaImpl.java:[9,8] com.example.JavaImpl is not abstract and does not override abstract method findById-WQTzO-4(java.util.UUID) in org.example.com.example.KotlinInterface
[ERROR] .../kotlin-interpolation/src/main/kotlin/com/example/JavaImpl.java:[11,5] method does not override or implement a method from a supertype
[ERROR] .../kotlin-interpolation/src/main/kotlin/com/example/JavaImpl.java:[13,16] Entity(java.util.UUID,java.lang.String) has private access in org.example.com.example.Entity
[ERROR] -> [Help 1]

Проект для воспроизведения проблемы вы можете найти здесь: github: Проблема интерполяции Kotlin

Есть ли способ правильно избежать такой проблемы? (Создание мостов, адаптеров или прокси-сервера невозможно.)

Примеры кода/конфигурации приветствуются.

UPD1: Благодаря чему-то мне удалось избежать этой проблемы, добавив аннотацию в интерфейс:

interface KotlinInterface<T> {

    @Suppress("INAPPLICABLE_JVM_NAME")
    @JvmName("findByIdForJava")
    fun findById(id: EntityId): T
}

и реализован в Java findByIdForJava(...):

public class JavaImpl implements KotlinInterface<Entity> {

    @Override
    public Entity findByIdForJava(@NotNull UUID id) {
        return new Entity(id, "Some name");
    }
}

НО, как упоминалось ниже Somethingsomething, мы столкнулись с проблемой компиляции для Entity, поэтому я придумал обходной путь, добавив статический конструктор для:

data class Entity(val id: EntityId, val name: String) {
    companion object {
        @JvmStatic
        fun createCompatible(id: UUID, name: String): Entity {
            return Entity(EntityId(id), name)
        }
    }
}

и используйте его в реализации Java:

    @Override
    public Entity findByIdForJava(@NotNull UUID id) {
        return Entity.createCompatible(id, "Some name");
    }

Этот вариант выглядит немного беспорядочно, но решает проблему совместимости.

Может быть, есть другие варианты исправить это правильно?

Требуется ли использование класса встроенных значений?

Steyrix 06.07.2024 11:45

Почему «создание мостов, адаптеров или прокси» не вариант?

Sweeper 06.07.2024 13:28

@Александр: Извините, я не проверил свой ответ должным образом. Я удалил его, так как не думаю, что он поможет решить вашу проблему.

Leviathan 06.07.2024 23:46

Связанный вопрос: stackoverflow.com/questions/69920555/…

Roar S. 07.07.2024 15:01

> Требуется ли использование класса встроенных значений? Да.

Alexandr 07.07.2024 19:17

>Почему «создание мостов, адаптеров или прокси» не вариант? Поскольку авторы Колтина утверждают, что Котлин полностью совместим с Java, хотелось бы иметь идиоматически правильный подход, иначе мы теряем преимущества Котлина.

Alexandr 07.07.2024 19:17
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
В компьютерном программировании биты играют важнейшую роль в представлении и манипулировании данными на двоичном уровне. Побитовые операции...
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Приходилось ли вам сталкиваться с требованиями, в которых вас могли попросить поднять тревогу или выдать ошибку, когда метод Java занимает больше...
Полный курс Java для разработчиков веб-сайтов и приложений
Полный курс Java для разработчиков веб-сайтов и приложений
Получите сертификат Java Web и Application Developer, используя наш курс.
1
7
68
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Проблема, о которой вы сообщили, вызвана искажением имен классов значений, это объяснено как в документации Kotlin (https://kotlinlang.org/docs/inline-classes.html#mangling), так и в соответствующем вопросе, который был ссылка в комментариях.

Вы можете просто сделать что-то вроде этого:

@Suppress("INAPPLICABLE_JVM_NAME")
@JvmName("foo")
fun findById(id: EntityId): T

И в вашем классе Java вместо этого реализуйте метод foo(...).

Однако после этого у меня возникла новая проблема:

 return new Entity(id, "Some name");

не получается с [ERROR] /tmp/kotlin-interpolation/src/main/kotlin/com/example/JavaImpl.java:[13,20] Entity(java.util.UUID,java.lang.String) has private access in org.example.com.example.Entity

Глядя на декомпилированный класс, мы видим 2 конструктора:

private Entity(UUID id, String name)
 // $FF: synthetic method
public Entity(UUID id, String name, DefaultConstructorMarker $constructor_marker)

Замена EntityId на UUID в классе Entity и перекомпиляция приводит к тем же конструкторам, за исключением того, что первый из них теперь общедоступен.

Поэтому мне кажется, что это какое-то недокументированное (насколько я могу найти) поведение, связанное с классами значений в общедоступных конструкторах, где конструктор становится частным, что блокирует работу вашего кода при вызове из Java.

Большое спасибо! Это большой прорыв! На самом деле я читал об искажении имен, но ничего не мог сделать, потому что в документации не упоминалось @Suppress("INAPPLICABLE_JVM_NAME"), поскольку @JvnName не разрешен для абстрактных методов. Частично направление получил, но с Entity занятием пока проблемы. Я бы не стал заменять EntityId -> UUID, иначе весь пост не имеет смысла.

Alexandr 10.07.2024 08:08

Другие вопросы по теме