Класс Generics в Котлине с двумя параметрами типа

class MapBuilder<T,U> {
    operator fun invoke(arg: T): MapBuilder<T, U> {
        return this
    }
    operator fun invoke(arg: U): MapBuilder<T, U> {
        return this
    }
}

конечно, это не работает из-за ограничений JVM.

Platform declaration clash: The following declarations have the same JVM signature (invoke(Ljava/lang/Object;)Lcom/test/tests/MapBuilder;):
    operator fun invoke(arg: T): MapBuilder<T, U> defined in com.test.tests.MapBuilder
    operator fun invoke(arg: U): MapBuilder<T, U> defined in com.test.tests.MapBuilder

Есть идеи, как я могу это реализовать?

Конечно, это невозможно, потому что, если я объявлю MapBuilder<String, String>, тогда не будет возможности различить, какой метод вызывать. Было бы полезно объяснить, чего вы пытаетесь достичь, чтобы получить лучший ответ.

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

Ответы 2

Это из-за конфликтов перегрузки.

Фактически, с вашими текущими параметрами, T может быть равен U. Если вы знакомы с перегрузкой, знайте, что это запрещено:

fun something(x: Int){ /* foo */ }
fun something(x: Int){ /* bar */ }

Но, например, это:

fun something(x: Int){ /* foo */ }
fun something(x: Float){ /* bar */ }

Поскольку они потенциально могут быть одинаковыми, это может привести к конфликтам. Как он узнает, какой метод вызвать?

При полном диапазоне компилятор пожалуется. Если вы используете : SomeClass с одним аргументом, он перестанет жаловаться. Но вот случайный пример:

class MapBuilder<T, U : Logger> {
    operator fun invoke(arg: T): MapBuilder<T, U> {
        return this
    }
    operator fun invoke(arg: U): MapBuilder<T, U> {
        return this
    }
}

fun t(){
    MapBuilder<Logger, Logger>().invoke(LoggerFactory.getLogger(""))
}

invoke будет неоднозначным. Проблема теперь существует только в том случае, если у вас есть два равных типа; что он использует?

Теперь ваш MCVE крайне минимален. Я не знаю, для чего вы используете T и U. В результате я не могу привести примеры кода. Но вот что вам нужно знать:

У вас не может быть двух из этих методов с любыми типами, потому что они могут конфликтовать. Даже использование дисперсии вызовет проблемы с перегрузкой, если вы используете два одинаковых типа. Таким образом, это исключило бы, например, MapBuilder<Int, Int>.

Вы можете использовать один метод или разделить их на два метода с разными именами. Название показывает, что это строитель, поэтому у вас могут быть withKey(T t) и withValue(U u).


Невозможно напрямую запретить T == U без передачи Class<T> и Class<U> и их проверки. К сожалению, компилятор этого не понимает даже с require или другими контрактными функциями. Кроме того, прежде чем вы попробуете, использование : Any не работает. Это граница по умолчанию. Помните, что все это Object на Java и Any на Kotlin.


Вы можете обойти это с помощью @JvmName (упомянутого в ответе Джейсон Минард), но вы должны использовать два разных имени метода, если вы взаимодействуете с Java. Это может быть немного проще, если вы используете только Kotlin. Взаимодействие Java-Kotlin имеет множество аннотаций @Jvm*, большинство / все из которых покрыты в документах.

Даже с @JvmName он все равно разрешит <String, String>, пока не будет вызван конфликтующий метод. Если вы хотите утверждать T! = U, несмотря ни на что, вам нужно будет запустить проверки классов.

Есть ли способы ограничить типы T и U, чтобы сделать их разными? запретить случаи <String, String>, <Int, Int> ?!

Dmitry Sokolov 16.11.2018 19:48

@DmitrySokolov не тогда, когда у тебя есть методы. Только что отредактировал ответ.

Zoe 16.11.2018 20:04

«У вас не может быть двух из этих методов с любыми типами, потому что они могут конфликтовать». Ваш пример с : Logger ясно показывает, что причина не в этом, потому что это разрешено, но методы все еще могут конфликтовать. Нет правила, запрещающего перегруженные методы, которые могут иметь неоднозначные вызовы.

Alexey Romanov 16.11.2018 20:41

@AlexeyRomanov да, но в первом случае у них одинаковые границы. Фактически что-то похожее на fun invoke(a: Any) и fun invoke(a: Logger). Очевидно, что разные типы имеют разный фактический вывод, но у него все еще нет соответствующего типа, в зависимости от общих типов. И мне хочется указать, что я сказал эффективно. Я не имею в виду то, для чего он компилируется, и не то, как это в точности работает.

Zoe 16.11.2018 20:44

@JaysonMinard, когда что-то технически невозможно, решить эту проблему напрямую невозможно. Generics не поддерживает U !extends T. Единственный способ подтвердить T! = U - это проверить Class <T> и Class <U>, то есть во время выполнения, и тогда уже слишком поздно. Компилятор все равно будет жаловаться на это

Zoe 16.11.2018 23:27

Если вы имеете в виду больше идей для замен, это требует больше кода из OP. В зависимости от того, что делают эти методы, они могут быть объединены, требуются отдельные имена или что-то еще. Это действительно зависит от использования. Поскольку все, что включено, это return this, создание примеров кода невозможно. Есть еще решения, включенные в виде текста, и этого более чем достаточно. Блоки кода не требуются строго.

Zoe 16.11.2018 23:33

Только что видел ваш ответ, используя @JvmName; это одно из решений. Это не делает меня неправильным

Zoe 16.11.2018 23:36

Вы правы, я упоминаю об этом сейчас в конце своего, указывая на вашу.

Jayson Minard 16.11.2018 23:43
Ответ принят как подходящий

Эти методы могут иметь одинаковую сигнатуру для неизвестных универсальных типов. Поэтому представленный базовый вариант неоднозначен для JVM. Поэтому вам просто нужно дать им альтернативное имя, с которого JVM (и Java или другой язык JVM) будет их просматривать. Вы используете аннотацию @JvmName для один или их обоих, чтобы дать им внутренние имена. Это не повлияет на Kotlin и имя, которое вы используете из своего кода Kotlin, которое будет видеть их такими, какими они были раньше.

class MapBuilder<T,U> {
    @JvmName("invokeWithT")
    operator fun invoke(arg: T): MapBuilder<T, U> {
        return this
    }

    @JvmName("InvokeWithU") // technically don't need both of these
    operator fun invoke(arg: U): MapBuilder<T, U> {
        return this
    }
}

Теперь у вас все в порядке, и вы можете использовать их самостоятельно.

val builder = MapBuilder<String, Integer>()
builder("hi") // success!
builder(123)  // success!

Имейте в виду, что если T и U неоднозначны, вы можете получить дополнительные ошибки при попытке их вызова.

val builder = MapBuilder<String, String>()
builder("hi") // error!

Error:(y, x) Kotlin: Overload resolution ambiguity:

@JvmName public final operator fun invoke(arg: String): MapBuilder defined in MapBuilder

@JvmName public final operator fun invoke(arg: String): MapBuilder defined in MapBuilder

Вы также можете обойти эту проблему, если можете определить свои обобщенные типы таким образом, чтобы они, возможно, не перекрывались и были одним и тем же классом. Вы мог бы получаете ошибку в зависимости от фактических выбранных общих параметров, но, по крайней мере, ваше базовое объявление будет разрешено. Более подробно это описано в Зои ответ.

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