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

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, несмотря ни на что, вам нужно запустить проверку классов.

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