【问题标题】:How to create map with generics in Kotlin?如何在 Kotlin 中使用泛型创建地图?
【发布时间】:2019-04-24 21:13:22
【问题描述】:

我需要创建一个 Map,其中键是类,值是适当类的对象。

喜欢:

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

我想使用泛型来避免“无效”条目,例如 Int::class to "Ten"

我该如何实现它?

【问题讨论】:

  • 我认为这是不可能的,但我很想被证明是错误的。 :-)
  • 我相信你可以编写一个专门的地图类来提供这种类型的安全性。但我不知道任何现有的。而且不知道能不能实现现有的Map接口。
  • 要将它们放在列表中,您将丢失类型信息。最后,这将是Map&lt;Any, Any&gt;

标签: generics kotlin


【解决方案1】:

我不太确定我是否得到了您真正想要完成的任务。不要忘记泛型在运行时会被删除,所以最后你只会有一个Map&lt;KClass&lt;*&gt;, Any&gt;(更准确地说是:Map&lt;Any, Any&gt;)。尽管如此,可能最简单的方法就是坚持你已经知道的。您已经展示了一种便捷方法 (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】:

    您使用泛型的示例实际上并未描述您对该地图的目标。 Map 接口上的泛型无法描述您想要的功能。键和值的类型需要封装您放入该映射的每个键和值,因此,这是可能的:

    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。由于 Kotlin 在泛型中推断类型的方式,T 可能具有相关但不相等的类型。示例:put(Number::class, 3) 甚至 put(Any::class, 3)。您可以使用reified 函数从参数推断类(因此put(3) 将是put&lt;Int&gt;(3)),但如果显式指定类型参数,这仍然可能被滥用。最终,您只能在运行时检查实际相等性,例如assert(key == value::class).
    • 另外,对于类似地图的类,我建议重载setget 运算符,以启用语法map[key]map[key] = value
    • @TheOperator 关于运算符重载的好建议
    • 我认为,如果你在他的回答中包含@xinaiz 构造函数扩展函数构建器构造函数,你将拥有一个非常完整的类 Map 类,可以满足你的要求。
    【解决方案3】:

    如果您希望地图在初始化后不可变,您可以这样做:

    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
    }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2017-12-06
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多