Я работаю над переносом веб-проекта с 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");
}
Этот вариант выглядит немного беспорядочно, но решает проблему совместимости.
Может быть, есть другие варианты исправить это правильно?
Почему «создание мостов, адаптеров или прокси» не вариант?
@Александр: Извините, я не проверил свой ответ должным образом. Я удалил его, так как не думаю, что он поможет решить вашу проблему.
Связанный вопрос: stackoverflow.com/questions/69920555/…
> Требуется ли использование класса встроенных значений? Да.
>Почему «создание мостов, адаптеров или прокси» не вариант? Поскольку авторы Колтина утверждают, что Котлин полностью совместим с Java, хотелось бы иметь идиоматически правильный подход, иначе мы теряем преимущества Котлина.




Проблема, о которой вы сообщили, вызвана искажением имен классов значений, это объяснено как в документации 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, иначе весь пост не имеет смысла.
Требуется ли использование класса встроенных значений?