Родовой класс в Котлине с двумя параметрами типа
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
Есть идеи, как мне это реализовать?
2 ответа
Эти методы могут эффективно иметь одну и ту же сигнатуру с неизвестными универсальными типами. Поэтому базовый случай в том виде, в котором он представлен, неоднозначен для 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!
Ошибка:(y, x) Kotlin: неоднозначность разрешения перегрузки:
@JvmName публичный финальный оператор fun invoke(arg: String): MapBuilder определен в MapBuilder
@JvmName публичный финальный оператор fun invoke(arg: String): MapBuilder определен в MapBuilder
Вы также можете обойти эту проблему, если вы можете определить свои дженерики таким образом, чтобы они, возможно, не перекрывались и не были одним и тем же классом. Вы можете получить ошибку в зависимости от выбранных фактических общих параметров, но по крайней мере ваше базовое объявление будет разрешено. Это описано более подробно в ответе Зои.
Это из-за перегрузки конфликтов.
Эффективно, с вашими текущими параметрами, 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
на Яве и Any
в Котлине.
Вы можете обойти это с помощью @JvmName (упомянуто в ответе Джейсона Минарда), но вы будете использовать два разных имени метода, если будете взаимодействовать с Java. Это может быть немного проще, если вы используете только Kotlin. Взаимодействие Java-Kotlin имеет кучу @Jvm*
аннотации, большинство / все из которых описаны в документации.
Даже с @JvmName это все еще позволит <String, String>
пока не будет вызван конфликтующий метод. Если вы хотите утверждать T!= U, несмотря ни на что, вам нужно запустить проверку классов.