如何在 Kotlin 中检查泛型类型

106

我正在尝试在 Kotlin 中测试一般类型。

if (value is Map<String, Any>) { ... }

但编译器报错如下:

无法检查已擦除类型的实例:jet.Map

对于普通类型的检查可以正常工作。

if (value is String) { ... }

使用的是 Kotlin 0.4.68 版本。

我在这里漏掉了什么?

6个回答

112
问题在于类型参数被擦除了,因此你无法针对完整的类型Map进行检查,因为在运行时没有关于这些String和Any的信息。 为了解决这个问题,可以使用通配符:
if (value is Map<*, *>) {...}

太好了!那个完美地运作了!我只是被文档中的例子搞混了:http://confluence.jetbrains.net/display/Kotlin/Type+casts - phil
75
如果您实际上想检查某个东西是否为Collection<String>以使其自动转换,该怎么办? - beruic
我有这样的代码片段 if (it.getSerializable(ARG_PARAMS) is HashMap<*, *>) {it.getSerializable(ARG_PARAMS) as HashMap<String, String>} else null。所以基本上,如果我检查泛型类型,它会尝试将 HashMap<String, Integer> 强制转换为 HashMap<String, String>。我有什么遗漏吗? - Farid
@FARID 是的,它会生效,但这种类型的强制转换并不安全。 - Andrey Breslav

30

我认为这是更合适的方式

inline fun <reified T> tryCast(instance: Any?, block: T.() -> Unit) {
    if (instance is T) {
        block(instance)
    }
}

使用方法

// myVar is nullable
tryCast<MyType>(myVar) {
    // todo with this e.g.
    this.canDoSomething()
}

另一种更短的方法

inline fun <reified T> Any?.tryCast(block: T.() -> Unit) {
    if (this is T) {
        block()
    }
}

使用方法

// myVar is nullable
myVar.tryCast<MyType> {
    // todo with this e.g.
    this.canDoSomething()
}

2
为什么 Kotlin stdlib 中没有直接提供这样的东西呢 :-( - ATom
something as? String”不是一样的吗?注意as后面有问号? - Dalibor Filus
1
@DaliborFilus 不是的。这是关于泛型和运行时擦除类型的问题。如果您不必处理泛型,那么可以直接使用 as?,对吗。 - stk

21

JVM会删除泛型类型信息。但是Kotlin拥有具体化的泛型。如果你有一个泛型类型T,你可以将内联函数的类型参数T标记为具体化,这样它就能在运行时进行检查。

因此,你可以这样做:

inline fun <reified T> checkType(obj: Object, contract: T) {
  if (obj is T) {
    // object implements the contract type T
  }
}

3
你能展示一下如何调用 checkType() 的例子吗?我不确定第二个参数应该传什么。 - Michael Osofsky
1
@MichaelOsofsky checkType(myMapOfStrings, Map<String, Any>) - Peter Graham

1

这是我使用的:

// Use this value if it is of type T, or else use defaultValue
inline fun <reified T> Any?.useIfTypeOrDefault(defaultValue: T) = 
    if (this is T) this else defaultValue

使用方法(kotest):

val map = mapOf("foo" to listOf("cheese"), "bar" to 666)

map["foo"].useIfTypeOrDefault<List<String>>(emptyList()).firstOrNull() shouldBe "cheese"
map["bar"].useIfTypeOrDefault<List<String>>(emptyList()).firstOrNull() shouldBe null

map["foo"].useIfTypeOrDefault<Number>(-1) shouldBe -1
map["bar"].useIfTypeOrDefault<Number>(-1) shouldBe 666

0

我会提供一个解决方案,但我认为它还算干净、比较好

try{
  (value as Map<String,Any>?)?.let { castedValue ->
     doYourStuffHere() //using castedValue
  }
}catch(e: Exception){
  valueIsNotOfType() //Map<String,Any>
}

-1

我已经尝试了上面的解决方案,使用tryCast<Array<String?>>,但在我的具体任务中,由于涉及许多强制转换,这不是一个好主意,因为它会严重降低性能。

这是我最终采用的解决方案 - 手动检查条目并调用方法,就像这样:

 fun foo() {
    val map: Map<String?, Any?> = mapOf()
    map.forEach { entry ->
        when (entry.value) {
            is String -> {
                doSomeWork(entry.key, entry.value as String)
            }
            is Array<*> -> {
                doSomeWork(entry.key, (entry.value as? Array<*>)?.map {
                    if (it is String) {
                        it
                    } else null
                }?.toList())
            }
        }
    }
}


private fun doSomeWork(key: String?, value: String) {

}
private fun doSomeWork(key: String?, values: List<String?>?) {

}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接