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.
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:
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:
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,
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.
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 ?
I personally don’t see anything wrong with docs here. Most importantly, this behavior is not that much related to as? operator, but rather to generics. If you use as, you will get similar problems. And the compiler/IDE even clearly states the cast doesn’t work. So don’t ignore warnings and you should be fine (filterIsInstance is another story though )
edit:
This is explained here: Generics: in, out, where | Kotlin Documentation . Documentation doesn’t explicitly say casting to T is not effective in this case, however, it explains what is unchecked cast and it says we need a reified type parameter to safely cast to T.
“as” has nothing to do with generics imho, I can use it without any generics in the whole code.
I never ignore warnings and even if I suppress it with an annotation I add a comment why that suppression is there.
The problem with docs is that “on failure” is ambiguous. Because there are two reasons for the failure: the instance is the wrong type or it is null.
Wrong type is a perfectly valid use case, I don’t get any warnings in this code:
interface A
class B : A
fun c(p : A) {
val d = p as B
}
fun e(p : A?) {
val d = p as B
val d2 = d as? B
}
fun e2(p : Any) {
val d = p as B
val d2 = d as? B
}
From the documentation:
To avoid exceptions, use the safe cast operator as?, which returns null on failure.
So, does this avoid the cast exception as well? Of course not, and that is correct. However, the sentence above is very misleading as it indicates that I’ll avoid ALL exceptions. There is nothing that describes that it avoids NullPointerException only.
So, it would be better to say:
To avoid NullPointerException, use the safe cast operator as?, which returns null if the subject of the cast is null.
This is a much better explanation of what as? actually does.
I’m not sure what you mean to be honest. as? avoids both the NullPointerException and ClassCastException. It returns null for both null as? String and 5 as? String. What is the part in your code that shows otherwise or what is the problem in the code exactly?
Well, you are right, it avoids ClassCastException as well, that’s my bad and my point exactly.
I was confused (again) went to the docs read this thread and arrived to the wrong conclusion. I simply can’t explain why, but I’m not the only one.
You could say that it’s my bad, and of course it is. But it would be so easy to help by adding a bit more information to the documentation.
Would it be really that hard to write it down explicitly? It’s like 2 sentences and it would be 100% clear what happens.
But I’m still not sure what would you like to clarify there. Just one comment ago you literally said the documentation says the operator does what it in fact really does. For some reason you expect a different behavior.
OP’s case was much different - it was due to generics.