Ответ 1
Да, вам не хватает важной детали реализации дженериков на JVM: стирание типа. В двух словах, скомпилированный байт-код классов фактически не содержит никакой информации об общих типах (за исключением некоторых метаданных о том, что класс или метод является общим). Вся проверка типов происходит во время компиляции, и после этого в коде не сохраняются общие типы, есть только Object
.
Чтобы обнаружить проблему в вашем случае, просто посмотрите на байт-код (в IDEA, Tools -> Kotlin -> Show Kotlin Bytecode
или любой другой инструмент). Рассмотрим этот простой пример:
interface Converter<T> {
fun convert(t: T): T
}
class Reverser(): Converter<String> {
override fun convert(t: String) = t.reversed()
}
В байтовом коде Converter
общий тип стирается:
// access flags 0x401
// signature (TT;)TT;
// declaration: T convert(T)
public abstract convert(Ljava/lang/Object;)Ljava/lang/Object;
И вот методы, найденные в байтекоде Reverser
:
// access flags 0x1
public convert(Ljava/lang/String;)Ljava/lang/String;
...
// access flags 0x1041
public synthetic bridge convert(Ljava/lang/Object;)Ljava/lang/Object;
...
INVOKEVIRTUAL Reverser.convert (Ljava/lang/String;)Ljava/lang/String;
...
Чтобы наследовать интерфейс Converter
, Reverser
должен иметь способ с той же сигнатурой, то есть стираемый тип. Если у фактического метода реализации есть другая подпись, добавляется метод моста. Здесь мы видим, что второй метод в байткоде - это именно метод моста (и он вызывает первый).
Таким образом, несколько универсальных реализаций интерфейса тривиально будут сталкиваться друг с другом, потому что может быть только один метод моста для определенной сигнатуры.
Кроме того, если это было возможно, ни Java, ни Kotlin не имеют перегрузки метода на основе типа возвращаемого значения, а также иногда возникают неоднозначности аргументов, поэтому несколько наследование было бы весьма ограниченным.
Вещи, однако, будут меняться с помощью Project Valhalla (reified generics сохранит фактический тип во время выполнения), но все же я не ожидал множественное наследование интерфейса.