Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Inferring a generic type of Map in Kotlin

Tags:

kotlin

Consider a Java method which infers its type by Java class as follows:

public <T> T readJson(Class<T> c) throws IOException {

This allows doing something like this:

Map<String, String> map = foo.readJson(Map.class);

In java it will warn about unchecked cast, but it will work correctly. However in Kotlin, this will not be so easy, one could try using:

foo.readJson(Map::class.java)

However if Map<String, String> will be required, it will not work:

Type inference failed. Expected type mismatch.
required Map<String, String>
found Map<*, *>!

I also tried defining an interface StringMap:

interface StringMap : Map<String, String>

However that does not work either, it will lead to exceptions like this:

Cannot cast ...LinkedTreeMap to ...StringMap

What would be a correct way of doing this?

like image 384
Vojtěch Avatar asked May 15 '17 14:05

Vojtěch


People also ask

How do I find my generic type Kotlin?

There are no direct ways to do this in Kotlin. In order to check the generic type, we need to create an instance of the generic class<T> and then we can compare the same with our class.

What is Mapof in Kotlin?

Kotlin map is a collection that contains pairs of objects. Map holds the data in the form of pairs which consists of a key and a value. Map keys are unique and the map holds only one value for each key. Kotlin distinguishes between immutable and mutable maps.

What is the difference between * and any in Kotlin generics?

When we define a collection with "*", it should contain the object of only that type. There should not be any mix and match between the data types inside a collection. If we use "Any", we can mix and match the data types, which means we can have multiple data types in a collection.


1 Answers

Kotlin does not have anything like Java raw types (which were left in Java for backward compatibility), and the type system therefore does not allow this kind of unchecked assignment to be made implicitly (star projections, the closest concept to raw types in Kotlin, retain type safety).

You can make an unchecked cast to Map<String, String>, thus expressing that you are aware of a possible type mismatch at runtime:

@Suppress("UNCHECKED_CAST")
val result = foo.readJson(Map::class.java) as Map<String, String>

You can suppress the unchecked cast warning for a broader scope than just one statement.

A natural improvement of this solution is writing a util function to hide the unchecked cast in it:

@Suppress("UNCHECKED_CAST")
inline fun <reified T: Any> JsonReader.readJson(): T {
    val result = readJson(T::class.java)
    return result as T
}

This solution uses an inline function with a reified type parameter: the function is transformed and substituted at each of its call sites, with T replaced by the specified (or inferred) type at compile time .

Usage examples:

val map = jsonReader.readJson<Map<String, String>>()

fun processMap(map: Map<String, String) { /* ... */ }

processMap(jsonReader.readJson()) // Map<String, String> is inferred for this call
like image 106
hotkey Avatar answered Oct 02 '22 19:10

hotkey