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
Есть идеи, как я могу это реализовать?




Это из-за конфликтов перегрузки.
Фактически, с вашими текущими параметрами, 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> ?!
@DmitrySokolov не тогда, когда у тебя есть методы. Только что отредактировал ответ.
«У вас не может быть двух из этих методов с любыми типами, потому что они могут конфликтовать». Ваш пример с : Logger ясно показывает, что причина не в этом, потому что это разрешено, но методы все еще могут конфликтовать. Нет правила, запрещающего перегруженные методы, которые могут иметь неоднозначные вызовы.
@AlexeyRomanov да, но в первом случае у них одинаковые границы. Фактически что-то похожее на fun invoke(a: Any) и fun invoke(a: Logger). Очевидно, что разные типы имеют разный фактический вывод, но у него все еще нет соответствующего типа, в зависимости от общих типов. И мне хочется указать, что я сказал эффективно. Я не имею в виду то, для чего он компилируется, и не то, как это в точности работает.
@JaysonMinard, когда что-то технически невозможно, решить эту проблему напрямую невозможно. Generics не поддерживает U !extends T. Единственный способ подтвердить T! = U - это проверить Class <T> и Class <U>, то есть во время выполнения, и тогда уже слишком поздно. Компилятор все равно будет жаловаться на это
Если вы имеете в виду больше идей для замен, это требует больше кода из OP. В зависимости от того, что делают эти методы, они могут быть объединены, требуются отдельные имена или что-то еще. Это действительно зависит от использования. Поскольку все, что включено, это return this, создание примеров кода невозможно. Есть еще решения, включенные в виде текста, и этого более чем достаточно. Блоки кода не требуются строго.
Только что видел ваш ответ, используя @JvmName; это одно из решений. Это не делает меня неправильным
Вы правы, я упоминаю об этом сейчас в конце своего, указывая на вашу.
Эти методы могут иметь одинаковую сигнатуру для неизвестных универсальных типов. Поэтому представленный базовый вариант неоднозначен для 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
Вы также можете обойти эту проблему, если можете определить свои обобщенные типы таким образом, чтобы они, возможно, не перекрывались и были одним и тем же классом. Вы мог бы получаете ошибку в зависимости от фактических выбранных общих параметров, но, по крайней мере, ваше базовое объявление будет разрешено. Более подробно это описано в Зои ответ.
Конечно, это невозможно, потому что, если я объявлю
MapBuilder<String, String>, тогда не будет возможности различить, какой метод вызывать. Было бы полезно объяснить, чего вы пытаетесь достичь, чтобы получить лучший ответ.