why List<String> can cast to List<Int> in Kotlin? - kotlin

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.

Related

Kotlin Function Generics - Upper Bound Not Working

I faced some issue regarding usage of Kotlin generics in functions
fun <T : CharSequence> doSomething(): T {
return String() as T
}
class Something(intValue: Int)
Something(doSomething()) // Doesn't show any compile error
Now when it is executed it throws error
java.lang.ClassCastException: class java.lang.String cannot be cast to class java.lang.Number
Wanted to know why Kotlin compiler is not throwing error for incompatible typecasting
I think what you are seeing is the major compiler bug KT-47664. Though in the bug report they used a much more complex example to demonstrate the issue, the cause of the bug is the same, that being the compiler has inferred an empty intersection type (the intersection of CharSequence and Int is empty) as the type parameter.
The algorithm apparently treats an empty intersection type the same as any other type, doesn't think anything special of it, and so type inference succeeds.
This bug has been fixed by KT-51221 Deprecate inferring type variables into an empty intersection type. From what I understand from reading the reports, there will now be a warning if an empty intersection type is inferred. However, the fix is only included in Kotlin 1.7.20+, which at the time of writing, is not released yet :(

Kotlin "no cast needed" in IDE

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

how to convert Java Map to read it in Kotlin?

I am facing some very basic problem (that never faced in java before) and might be due my lack of knowledge in Kotlin.
I am currently trying to read a YML file. So Im doing it in this way:
private val factory = YamlConfigurationFactory(LinkedHashMap::class.java, validator, objectMapper, "dw")
Best on Dropwizard guide for configurations.
https://www.dropwizard.io/1.3.12/docs/manual/testing.html
So later in my function I do this"
val yml = File(Paths.get("config.yml").toUri())
var keyValues = factory.build(yml)
When using my debugger I can see there is a Map with key->values, just as it should be.
now when I do keyValues.get("my-key")
type inference failed. the value of the type parameter k should be mentioned in input types
Tried this but no luck
var keyValues = LinkedHashMap<String, Any>()
keyValues = factory.build(yml)
The YamlConfigurationFactory requires a class to map to, but I dont know if there is a more direct way to specify a Kotlin class than with the current solution +.kotlin, like
LinkedHashMap::class.java.kotlin
Here it also throws an error.
Ideas?
Well, this is a typical problem with JVM generics. Class<LinkedHashMap> carries no info on what are the actual types of its keys and values, so the keyValues variable always ends up with the type LinkedHashMap<*, *> simply because it can't be checked at compile time. There are two ways around this:
Unsafe Cast
This is how you would deal with the problem in standard Java: just cast the LinkedHashMap<*, *> to LinkedHashMap<String, Any> (or whatever is the actual expected type). This produces a warning because the compiler can't verify the cast is safe, but it is also generally known such situations are often unavoidable when dealing with JVM generics and serialisation.
YamlConfigurationFactory(LinkedHashMap::class.java, ...) as LinkedHashMap<String, Any>
Type Inference Magic
When using Kotlin, you can avoid the cast by actually creating instance of Class<LinkedHashMap<String, Any>> explicitly. Of course, since this is still JVM, you lose all the type info at runtime, but it should be enough to tell the type inference engine what your result should be. However, you'll need a special helper method for this (or at least I haven't found a simpler solution yet), but that method needs to be declared just once somewhere in your project:
inline fun <reified T> classOf(): Class<T> = T::class.java
...
val factory = YamlConfigurationFactory(classOf<LinkedHashMap<String, Any>>(), ...)
Using this "hack", you'll get an instance of LinkedHashMap directly, however, always remember that this is just extra info for the type inference engine but effectively it just hides the unsafe cast. Also, you can't use this if the type is not known at compile type (reified).

Cast reflection "Type" to a List<class> in Kotlin

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>

Cast Any to Array in Kotlin

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.