backcab
backcab

Reputation: 668

Kotlin - Generic function storing incorrect type in map successfully

Consider the following code:

val myarray = arrayListOf("hello", "world")
println(myarray)
println(myarray::class.java.name)

val arrayString = mapper.writeValueAsString(myarray)
println(arrayString)
println(arrayString::class.java.name)

val myMap = HashMap<String, String?>()
myMap["key"] = JsonPath.read<String>(arrayString, "$")

println(myMap["key"])
println(myMap["key"]!!::class.java.name)

This code creates an ArrayList containing the values "hello" and "world", converts the list to a json string, reads the root element of json (which is a generic function told to return a string) and stores it in a HashMap

This code compiles and runs without error and produces the following output:

[hello, world]
java.util.ArrayList
["hello", "world"]
java.lang.String
["hello","world"]
net.minidev.json.JSONArray

How does myMap["key"] return a JSONArray? (recall that myMap was declared as <String, String?>)

Kotlin its type checked at compile time, but because the generic function JsonPath.read is told to return a String, compilation happens fine.

The JsonPath.read then seems to violate its contract and returns a JSONArray instead of a String. Kotlin does not seem to type check this and allows storage of the JSONArray within a Map<String, String?>.

No errors are produced unless I attempt to use myMap["key"] as a String

NOTE

This problem can be resolved by changing the line that reads the json to:

myMap["key"] = ObjectMapper().writeValueAsString(JsonPath.read<String>(arrayString, "$"))

EDIT

Code example not using json madness:

fun <T> myFun(): T {
  return 7 as T
}

fun test() {
  val map = HashMap<String, String?>()
  map["key"] = myFun<String>()
  println(map["key"])
  println(map["key"]!!::class.java.name)
}

returns:
7
java.lang.Integer

Upvotes: 0

Views: 232

Answers (2)

Ilya
Ilya

Reputation: 23115

A similar problem is tracked as https://youtrack.jetbrains.com/issue/KT-12451.

Currently Kotlin doesn't check the result of a generic method call before passing it to another generic method, even if at the call site it knows the reified type of the generic type parameter. This behavior is same as in Java, though there could be some room for improvement.

Upvotes: 0

Roland
Roland

Reputation: 23242

Generic type information is erased at runtime.

So basically just taking your updated example and translating it to what it will be at runtime (simplified):

fun myFun(): Any = 7

fun test() {
  val map = HashMap<Any, Any?>()
  map["key"] = myFun()
  println(map["key"])
  println(map["key"]!!::class.java.name) // of course: Integer!
}

Note also that you were using an unchecked cast here (and most probably also JsonPath.read is using it), which basically is the reason why the compiler has no real chance to catch the problem. Or in other words, by using an unchecked cast (7 as T) you basically say to the compiler "hey... I know what I am doing, don't bother"... and so it doesn't bother ;-)

Upvotes: 1

Related Questions