Как создать карту с дженериками в Котлине? - PullRequest
2 голосов
/ 25 апреля 2019

Мне нужно создать карту, где ключи - это классы, а значения - объекты соответствующих классов.

Как:

mapOf<KClass<T>, T>(
    Int::class to 10,
    String::class to "Ten"
)

Я хочу использовать непатентованные значения, чтобы избежать «недействительных» записей, например Int::class to "Ten"

Как я могу это реализовать?

Ответы [ 3 ]

2 голосов
/ 25 апреля 2019

Я не уверен, получу ли я то, чего вы действительно хотите достичь. Не забывайте, что генерики стираются во время выполнения, так что в конце вы просто получите Map<KClass<*>, Any> (точнее: Map<Any, Any>). Тем не менее, возможно, самый простой способ - просто придерживаться того, что вы уже знаете. Вы уже показали удобный метод (to) для создания Pair, который затем передается в mapOf, так почему бы просто не использовать новую функцию, соответствующую вашим требованиям, например,

inline fun <reified T : Any> typedPair(value : T) = Pair(T::class, value)

Так что вы можете использовать:

mapOf(
  typedPair(10), // adds Int::class as key with 10 as value
  typedPair<Short>(1) // adds Short::class as key with 1 as value
  typedPair<Number>(2) // adds Number::class as key with 2 as value
)

Конечно, таким образом вы все еще можете добавить любое другое созвездие в эту карту. Если вы хотите преодолеть это, у вас есть еще несколько вариантов:

Как насчет создания дополнительной функции typedMapOf, например ::

fun typedMapOf(vararg values : Any) = values.associateBy { it::class }

Использование этого может выглядеть следующим образом:

typedMapOf(10, "string", 1.toShort())

Однако вам, вероятно, будет трудно добавить Number::class тогда; -)

Вы также можете смешать два варианта выше, например:

data class MyTypedPair<T : Any>(val type : KClass<T>, val value : T)
inline fun <reified T : Any> typedPair(value : T) = MyTypedPair(T::class, value)
fun typedMapOf(vararg values : MyTypedPair<*>) = values.associateBy({it.type}) { it.value }

Что теперь в основном заставляет вас создавать специальный тип для создания этой типизированной карты.

У меня все еще есть другие варианты ... У вас также может быть что-то вроде оболочки, которая просто поддерживает минимальный набор функций:

class MyValues {
    private val backedMap = mutableMapOf<KClass<*>, Any>()
    fun <T : Any> put(value : T) = backedMap.put(value::class, value)
    operator fun <T : Any> get(key : KClass<T>) = backedMap[key]
}

Использование тогда немного отличается от Map, но все еще очень просто:

MyValues().apply {
  put(10)
  put<Short>(1)
}

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

2 голосов
/ 25 апреля 2019

Ваш пример использования обобщений на самом деле не описывает вашу цель для этой карты.Обобщения в интерфейсе карты не способны описать ту функциональность, которую вы хотите.Типы ключей и значений должны были бы инкапсулировать каждый ключ и значение, введенные в эту карту, поэтому это возможно:

val myInstanceMap = mapOf<KClass<*>, Any>(
        Int::class to 10,
        String::class to "10"
)

Чтобы обеспечить безопасность типов для определенных ключей и значений в этой картеВы должны будете сделать некоторые свои собственные работы, чтобы обернуть такую ​​общую карту.Вот пример:

class ClassToInstanceMap {

    private val backingMap = mutableMapOf<KClass<*>, Any?>()

    operator fun <T: Any> set(key: KClass<T>, value: T) {
        backingMap[key] = value
    }

    @Suppress("UNCHECKED_CAST")
    operator fun <T: Any> get(key: KClass<T>): T {
        return backingMap[key] as T
    }

    fun containsKey(key: KClass<*>): Boolean {
        return backingMap.containsKey(key)
    }

}

fun main() {
    val classToInstanceMap = ClassToInstanceMap()

    classToInstanceMap[Int::class] = 1
    val intInstance = classToInstanceMap[Int::class]
    println(intInstance)

    classToInstanceMap[Int::class] = 2
    val intInstance2 = classToInstanceMap[Int::class]
    println(intInstance2)

    classToInstanceMap[String::class] ="1"
    val stringInstance = classToInstanceMap[String::class]
    println(stringInstance)

    classToInstanceMap[String::class] ="2"
    val stringInstance2 = classToInstanceMap[String::class]
    println(stringInstance2)
}

Я уверен, что вы можете выяснить, как реализовать другие общие методы карты из этого.

1 голос
/ 25 апреля 2019

Если вы хотите, чтобы ваша карта была неизменной после инициализации, вы можете сделать это:

import kotlin.reflect.KClass

class InstanceKeyMapper(initBlock: InstanceKeyMapper.() -> Unit) {

    private val map = mutableMapOf<KClass<*>, Any>()

    init {
        initBlock(this)
    }

    infix fun <T : Any> KClass<T>.instance(value: T) {
        map[this] = value
    }

    fun toMap() = map as Map<KClass<*>, Any> // downcast to disable mutability

}

fun instanceMapOf(initBlock: InstanceKeyMapper.() -> Unit) = InstanceKeyMapper(initBlock).toMap()

И использовать ее следующим образом:

fun main(args: Array<String>) {

    val map = instanceMapOf {
        Int::class instance 42 // ok
        String::class instance "abc" // ok
        Float::class instance 3.14f // ok
        Boolean::class instance true // ok
        Long::class instance "not legit" // bad type, compilation error
    }

    println(map[Int::class]) // 2
    println(map[String::class]) // "abc"
    map[Long::class] = 123L // compilation error, read-only
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...