Safe cast operator (as?) throws exception


#1

Hello!

fun <T> cast(from: Any): T? = from as? T

val x = cast<String>(42) // java.lang.ClassCastException: java.lang.Integer cannot be cast to java.lang.String

Is it expected behaviour? I thought as? operator should suppress exception and return null.

Thanks!


#2

Warning: Unchecked cast: Any to T

This is caused by type erasure.

Try:

inline fun <reified T> cast(from: Any): T? = from as? T

#3

The question mark is usually used in Kotlin for nullability. The “as?” also allows the cast to return null when the left-hand side is null. See the documentation:

https://kotlinlang.org/docs/reference/typecasts.html#safe-nullable-cast-operator

It will not allow the above code.


#4

Thank you @fvasco!

But this is simplified example of real code to show the problem.
I can’t use reified T in real code.


#5

Thank you @Tim_van_der_Leeuw.1!

It is written in docs : To avoid an exception being thrown, one can use a safe cast operator as? that returns null on failure.
I thought that “failure” means not only case when left-side is null, for example this works well:

val x = 42 as? String // x initialised to null

#6

Unless parameter T is reified, it’s treated as Any, so basically you wrote the following function: fun cast(from: Any): Any? = from as? Any. Obviously it works for anything and compiler adds a different check at call site which throws exception. Basically that’s limitation of type erasure inherited from JVM. In Java you would pass Class<T> instance, in Kotlin you can do the same or use reified modifier which will do the same under the hood,


#7

Thank you @vbezhenar!

But I still don’t understand why this works:

val x = cast<String>("some str")
val x = cast<Int>(42)

What is more I thought that as? operator is some kinda shorthand for this:

fun <T> cast(from: Any): T? = try {
    from as T
} catch (e: ClassCastException) {
    null
}

Which works perfectly. Am I wrong?


#8

The cast which throws the ClassCastException in your example is not the as? operator, it’s a cast inserted by the compiler where you assign the return value of a generic method (which is actually Any because of type erasure) to the variable of a specific type.


#9

@yole thanks for your reply!

If I understand you correctly, you tell that ClassCastException raised after the as? operator.
But at that time result should already be null.
Why ClassCastException message tells : java.lang.Integer cannot be cast to java.lang.String ?


#10

No, the result will not be null because generic types are erased at runtime. Your cast function performs a cast to Any, which always succeeds.


#11

Thank you @yole!

Now I understand. So there is no way to write such function, maybe if I pass KClass as argument?


#12

It works if you use inline and reified type T. That prevents type-erasure.

The following worked from me in the Kotlin REPL:

inline fun <reified T> cast(any: Any?) : T? = any as? T?

cast<String>(14)
null

#13

You can either use a reified type parameter or indeed pass KClass as an argument.