How can I check for generic type in Kotlin

Generic ProgrammingKotlin

Generic Programming Problem Overview


I'm trying to test for a generic type in Kotlin.

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

But the compiler complains with

> Cannot check for instance of erased type: jet.Map

The check with a normal type works well.

if (value is String) { ... }

Kotlin 0.4.68 is used.

What am I missing here?

Generic Programming Solutions


Solution 1 - Generic Programming

The problem is that type arguments are erased, so you can't check against the full type Map, because at runtime there's no information about those String and Any.

To work around this, use wildcards:

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

Solution 2 - Generic Programming

I think this is more appropriate way

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

Usage

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

Another shorter approach

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

Usage

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

Solution 3 - Generic Programming

JVM removes the generic type information. But Kotlin has reified generics. If you have a generic type T, you can mark type parameter T of an inline function as reified so it will be able to check it at runtime.

So you can do:

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

Solution 4 - Generic Programming

I'm gonna give a workaround solution but I think its clean, kind of

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

Solution 5 - Generic Programming

I have tried the solution above with tryCast<Array<String?>> and, I guess, in my specific task in listing with many castings involved it was no so great idea, because it was slowing the performance drastically.

This is the solution I did finally - manually check the entries and call methods, like this:

 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?>?) {

}

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionphilView Question on Stackoverflow
Solution 1 - Generic ProgrammingAndrey BreslavView Answer on Stackoverflow
Solution 2 - Generic ProgrammingVladView Answer on Stackoverflow
Solution 3 - Generic ProgrammingmennoView Answer on Stackoverflow
Solution 4 - Generic ProgrammingAntoine El MurrView Answer on Stackoverflow
Solution 5 - Generic ProgrammingVarvara SolovyevaView Answer on Stackoverflow