I'm initializing a class by loading data from a Map<String, Any> in Kotlin. As this Map is gleaned directly from JSON, I don't know for certain that any given key exists, or that its value is of the type I expect. To unpack this Map safely I'm doing the following, which appears to work perfectly:
a = rawData["A"] as? String ?: ""
Some of this data is in further nested JSON, which I'm unpacking to Arrays; I've tried to do this in the same way:
b = rawData["B"] as? Array<String> ?: arrayOf<String>()
However, when I attempt this using an array (as above) IntelliJ kicks up a fuss, saying
Warning:(111, 30) Kotlin: Unchecked cast: Any? to Array<String>
Is this just the IDE getting itself in a twist or is this method genuinely unsafe for Arrays despite being seemingly perfectly safe for other types?
For any future readers of this question, to expand on the accepted answer with a solution:
To safely cast Any to an array of a particular type in Kotlin, you have to first cast to an untyped array (see zsmb13's answer above for why), and then filter that array to the desired type.
For example, to cast input: Any to an array of String instances, you would call:
val inputAsArray = (input as? Array<*>)?.filterIsInstance<String>()
I was ready to call this a bug, because Array is a reified type, meaning its generic parameter can actually be checked at runtime (unlike a List, for example). I've tried looking to see if it's been filed yet, and it turns out the compiler is actually right to show you a warning. As you can see in the response to this issue, there's still a nullability problem with casts of this kind.
val a = arrayOf("foo", "bar", null) as Array<String>
println(a[2].length)
Arrays like the one in this example are successfully cast (using as, they don't throw an exception, using as?, they don't return null), however, the cast can not ensure that this is an Array<String>, only that it's an Array<String?>.
This means that you can later read null values from a variable that is typed as an Array<String> after the cast, with no further warnings from the compiler.
Related
why below code can run?
fun main(args: Array<String>) {
val somePair: Pair<Any?, Any?> = "items" to listOf("1","2a")
var W=somePair.second as List<Int>
println(W)
}
output:[1, 2a]
"2a" is not Int , but the type of W is List, why no exception throw?
Compiling your code gives the following warning:
Unchecked cast: Any? to List<Int>
This literally means that the cast will not throw an exception in all cases. If you choose to ignore this warning, you should be prepared for this behaviour to happen.
Now as to why this actually runs fine, keep in mind that generic types are erased at runtime. This means that a List<String> or a List<Int> is actually just a raw List at runtime, the information about the element type is lost.
The point of the generics is mostly to help when compiling the code: the compiler prevents you from adding elements of the wrong type to the list, and it helps you when you read elements from the list by giving you a value with the proper type (no cast required).
Casts, however, operate at runtime. They check the runtime type of an object against a given type. The type List<Int> that you cast to is a generic type, and the compiler warns you because at runtime it will only see the raw interface List.
More concretely, somePair.second as List<Int> compares the runtime type of somePair.second (which is ArrayList here) with the raw List interface - and that is a match, so no exception.
When casting a variable at the right side of the assign, i'm surprisely realize that the variable still behave as the casted type and not as it was original defined.
Am i doing something wrong or it's a compiler issue?
Code:
val hippoList = listOf<Hippo>(Hippo())
val hippoMutableList : MutableList<Hippo> = hippoList as MutableList<Hippo>
hippoList.add(Hippo())
since hippoList is from a List type, it is immutable. So how does trying to run add function on an immutable type isn't cause to compilation error?
If you're doing casting it means that you know more than a compiler about this context of execution and you're telling the compiler that this hippoList is a MutableList so on every next usage of hippoList compiler already knows that this have to be a MutableList and allows you to use add method because you casted it to MutableList previously. In fact you will get a runtime error UnsupportedOperationException which means that you didn't really know more about this context of execution and you did something wrong. So instead of using casting on your own allow compiler to do it's work.
In your case instead of a casting to MutableList, transform hippoList to MutableList with
hippoList.toMutableList()
The same happens when you're using !! from nullable type to not null type, when you're using it when you know more than a compiler about the context of execution. Here's a little example
val someNullableType: String? = null
val thisStringIsNotNull = someNullableType!!
by using !! on someNullableType we're telling the compiler that someNullableType is not null as well, so we're allowed to write (as in you're case where you're telling that your List is a MutableList as well)
someNullableType.length
but we will receive exception earlier (in place where we used !! to tweak the compiler)
I'am new to Kotlin (and Java) so may be a stupid question, but IntelliJ keeps telling me "No cast needed" on the second function call. If i switch the order of the functions the same for the other functions.
I could imagine 2 things:
Kotlin is smart it knows: Hey first cast is fine, so i will cast the second
IntelliJ problem ?
(this as Exec).setVersionToDeploy()
(this as Exec).setEcsTaskMemory()
Both functions are defined as (Gradle-Plugin):
fun Exec.XX()
Your first guess is correct!
This is known as a smart cast: the compiler knows that, if execution reaches your second line, the type of this must be Exec (else the first line would have thrown a ClassCastException and it wouldn't have reached the second line). So it infers the specific type, and a further cast is not needed
In general, the compiler infers types in cases such as this, so you don't need to cast explicitly. (It's not an error to do so, only a warning; but IDEA is very keen on showing ways your code can be improved.)
You see this most commonly with nullability (since that's part of the type system). For example, if you have a nullable field, the compiler won't let you call its methods directly:
val myString: String? = "abc"
println(myString.length) // COMPILE ERROR, as myString could be null
but if you add a manual check, the compiler smart-casts the field to its non-nullable type, so you don't need a cast:
val myString: String? = "abc"
if (myString != null)
println(myString.length) // OK; compiler infers type String
In my Kotlin code, I have a variable that is the Type interface from
java.lang.reflect
var type: Type
But I need to cast this to:
List<UserInfo>
If I was not casting to a List, I would just do this:
var type = UserInfo::class.java
and this works. But I don't know how to cast it using a List. The closest I found is this:
var type = Array<UserInfo>::class.java
This would compile if my UserInfo was an Array but it's a List.
The issue (as #Miha_x64 says) is type erasure.
The Java Virtual Machine knows nothing about type parameters. So although the source specified a List<UserInfo>, it compiles down to a plain List.
So this works:
var type = List::class.java
(Your Array example works because arrays are a special case: they're directly supported in the JVM, and keep their types at runtime.)
Java's use of type erasure is at least partly for historical reasons; when generics were added to Java 5, they wanted to preserve compatibility with existing source and bytecode. For all the gory details (much of which is inherited by Kotlin), see here.
Maybe its late. Try to use KTypeProjection and createType() with KClass instead. Then get the value in KType or convert it back to java Type.
val kClass = YourClass::class
val kTypeProjection = KTypeProjection.invariant(entity.starProjectedType)
val kType = List::class.createType(listOf(kTypeProjection))
val type = kType.javaType
result:
kType: kotlin.collections.List<YourClass>
type: java.util.List<YourClass>
I'm trying to apply Observable.zip to a list of observables. It works but I would like it to conserve the type information. The problem happens with the snippet below:
val observable1 = Observable.fromArray(1, 2, 3)
val observable2 = Observable.fromArray(1, 2, 3)
// result is a Array<Any>. Could it be an Array<Int> instead ?
val result = Observable.zip(listOf(observable1, observable2), {a -> a}).blockingFirst()
It feels like zip should be able to know that my items are of type Int. In this very specific case, I could certainly cast but I also have more complex types and would feel much better if I could conserve type. Do I miss something ?
Unfortunately, the Java (and Kotlin) type system doesn't let you (or us library writers) do such type preservation; you have to manually cast back the a Object[] elements to their respective type.
This is done for you with the 2-9 argument zip() overloads and the zipWith instance operator.
The underlying problem is that one can't create a generic array as well as the type erasure itself. If we allowed Function<T[], R> in the signature, we would still have to create new Object[] for the invocation. However, your Function<Integer[], R> implementation would try to cast Object[] into Integer[] which is not allowed and fails with ClassCastException.
This is also described in the zip javadoc:
Note on method signature: since Java doesn't allow creating a generic array with new T[], the implementation of this operator has to create an Object[] instead. Unfortunately, a Function<Integer[], R> passed to the method would trigger a ClassCastException.