Родовой класс в Котлине с двумя типами параметров - PullRequest
0 голосов
/ 16 ноября 2018
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 ]

0 голосов
/ 17 ноября 2018

Эти методы могут эффективно иметь одну и ту же сигнатуру с неизвестными универсальными типами. Поэтому базовый случай в том виде, в котором он представлен, неоднозначен для JVM. Поэтому вам просто нужно дать им альтернативное имя, из которого JVM (и Java или другой язык JVM) будет их просматривать. Вы используете аннотацию @JvmName для one или для них обоих, чтобы дать им внутренние имена. Это никак не повлияет на 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) Котлин: Неоднозначность разрешения перегрузки:

@ JvmName публичный финальный оператор fun invoke (arg: String): MapBuilder определен в MapBuilder

@ JvmName публичный финальный оператор fun invoke (arg: String): MapBuilder определен в MapBuilder

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

0 голосов
/ 16 ноября 2018

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

Эффективно, с вашими текущими параметрами, 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 не работает. Это ограничение по умолчанию. Помните, что в Java все равно Object, а в Котлине Any.


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

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

...