Type- or Class-keyed map - kotlin

In Kotlin I can use filterIsInstance to obtain a type-specific (and type-safe) sub-collection:
val misc: List<Any> = listOf(42, 3.14, true, "foo", "bar")
val strings: List<String> = misc.filterIsInstance<String>()
println(strings) // => [foo, bar]
But I have a large collection of objects and I would like to pre-sort it into a Map, by concrete type. Is it even possible to define such a map in Kotlin's type system?
val miscByType: Map<KType, Collection<???>>
or:
val miscByClass: Map<KClass, Collection<???>>
Should I use a custom implementation with unsafe (but logically sound) casts?
The following is such an implementation. It works, but I'm wondering if there is a less hacky way of doing it:
import kotlin.reflect.KClass
class InstanceMap {
// INVARIANT: map from KClass to a set of objects of *that concrete class*
private val map: MutableMap<KClass<*>, MutableSet<Any>> = mutableMapOf()
// this is the only public mutator, it guarantees the invariant
fun add(item: Any): Boolean =
map.getOrPut(item::class) { mutableSetOf() }.add(item)
// public non-inline accessor, only needed by the inline accessor
fun get(cls: KClass<*>): Set<*>? = map[cls]
// inline accessor that performs an unsafe, but sound, cast
#Suppress("UNCHECKED_CAST")
inline fun <reified T> get(): Set<T> = get(T::class) as Set<T>? ?: setOf()
}
fun instanceMapOf(vararg items: Any): InstanceMap = InstanceMap().apply {
items.forEach { add(it) }
}
val misc = instanceMapOf(42, 3.14, true, "foo", "bar")
val strings = misc.get<String>()
println(strings) // => [foo, bar]

Your code looks OK. The only problem is with the unchecked cast warning. At the JVM bytecode level, the cast does nothing, because of the way, how generics are implemented in Java and Kotlin. It is also known as type erasure.
https://en.wikipedia.org/wiki/Type_erasure
Type erasure adds yet another problem to your code - it does not tell generic type arguments. So that, for example, List<Int> has the same class as List<String> or List<Map<String, Object>>
Do you expect your code to find superclasses or interfaces in the map? E.g. if I have
interface A
interface B
class C : A, B
val m = InstanceMap()
m.add(C())
m.get(C::class)
m.get(A::class)
m.get(B::class)
Do you expect these 3 calls to return the same value?
The JVM standard workaround for it is to explicitly pass Class<T> parameter and use the Class#cast method to cast instead. That change will make the code safer.
There is a remedy to type erasure in Kotlin. You may add a reified inline function so that Kotlin compiler will use the exact type in the inlined generic function body
https://kotlinlang.org/docs/reference/inline-functions.html#reified-type-parameters
inline fun <reified T> InstanceMap.get() = get(T::class)

Related

Get type of generic object in Kotlin

I saw many similar topics, but did not find suitable for me.
I have a parent class sealed class Item
and a few nested:
data class A(val a: Int) : Item()
data class B(val b: Int) : Item()
There are a lists:
val aList = listOf<A>(...)
val bList = listOf<B>(...)
and some method which take a list as argument:
fun processList(list: List<Item>) {...}
How to get specific type in the processList method?
I know about the way with reified type using:
inline fun <reified T: Item> processList(list: List<T>) {
val type = T::class
...
}
but it cannot work with vararg arguments:
fun processList(vararg items: List<Item>) {...}
which is called for example processList(aList, bList).
Is it possible to get type of list elements without generics?
The only idea is get type of first list element, but it is not a very nice solution.
Disclaimer: author of this question explained in comments that they need a way to receive vararg of lists and then get the parameter type of each list separately.
General answer is that this is not possible. Due to type erasure in Java/Kotlin, generic types are not known at runtime. reified introduced by Kotlin is an attempt to mitigate this problem at least partially, although it has its limitations as well. There are some ways to accomplish this, but they're all more like workarounds than real solutions:
1. Do not use vararg, but overload the function.
inline fun <reified T1: Item> processList(list1: List<T1>) {}
inline fun <reified T1: Item, T2: Item> processList(list1: List<T1>, list2: List<T2>) {}
inline fun <reified T1: Item, T2: Item, T3: Item> processList(list1: List<T1>, list2: List<T2>, list3: List<T3>) {}
...
Sounds really bad, but it takes 10 minutes to write such functions for up to 10 params and you do this once. Many libraries do something similar.
You can delegate each above function to a single one that accepts a list/array of KClass/Class, so your real implementation will be in a single place.
2. Check the first item of each list.
Very hacky and it has its limitations. You don't get the compile type, but the runtime type, which depending on your case may be not at all acceptable. It won't work well with heterogeneous collections. Also, if you need to know the type of empty lists, then this is not possible.
3. Create your own implementation of a list which keeps its type.
fun main() {
val aList = myListOf<A>()
val bList = myListOf<B>()
processList(aList, bList)
}
// or:
fun main() {
val aList = listOf<A>()
val bList = listOf<B>()
processList(aList.asMyList(), bList.asMyList())
}
fun processList(vararg items: MyList<Item>) {
println(items[0].type)
println(items[1].type)
}
inline fun <reified T : Any> myListOf(vararg items: T): MyList<T> = MyList(T::class, listOf(*items))
inline fun <reified T : Any> List<T>.asMyList(): MyList<T> = MyList(T::class, this)
class MyList<out T : Any> #PublishedApi internal constructor(
val type: KClass<out T>,
private val delegate: List<T>
) : List<T> by delegate
Of course, it has the obvious limitation that we have to use our specific list implementation.

Generic inline function

Let's say I have an object which helps me to deserialize other objects from storage:
val books: MutableList<Book> = deserializer.getBookList()
val persons: MutableList<Person> = deserializer.getPersonList()
The methods getBookList and getPersonList are extension functions I have written. Their logic is allmost the same so I thought I may can combine them into one method. My problem is the generic return type. The methods look like this:
fun DataInput.getBookList(): MutableList<Book> {
val list = mutableListOf<Book>()
val size = this.readInt()
for(i in 0 .. size) {
val item = Book()
item.readExternal(this)
list.add(item)
}
return list
}
Is there some Kotlin magic (maybe with inline functions) which I can use to detect the List type and generify this methods? I think the problem would be val item = T() which will not work for generic types, right? Or is this possible with inline functions?
You cannot call the constructor of a generic type, because the compiler can't guarantee that it has a constructor (the type could be from an interface). What you can do to get around this though, is to pass a "creator"-function as a parameter to your function. Like this:
fun <T> DataInput.getList(createT: () -> T): MutableList<T> {
val list = mutableListOf<T>()
val size = this.readInt()
for(i in 0 .. size) {
val item = createT()
/* Unless readExternal is an extension on Any, this function
* either needs to be passed as a parameter as well,
* or you need add an upper bound to your type parameter
* with <T : SomeInterfaceWithReadExternal>
*/
item.readExternal(this)
list.add(item)
}
return list
}
Now you can call the function like this:
val books: MutableList<Book> = deserializer.getList(::Book)
val persons: MutableList<Person> = deserializer.getList(::Person)
Note:
As marstran mentioned in a comment, this requires the class to have a zero-arg constructor to work, or it will throw an exception at runtime. The compiler will not warn you if the constructor doesn't exist, so if you pick this way, make sure you actually pass a class with a zero-arg constructor.
You can't initialize generic types, in Kotlin or Java. At least not in the "traditional" way. You can't do this:
val item = T()
In Java, you'd pass a Class<T> and get the constructor. Very basic example of that:
public <T> void x(Class<T> cls){
cls.getConstructor().newInstance(); // Obviously you'd do something with the return value, but this is just a dummy example
}
You could do the same in Kotlin, but Kotlin has a reified keyword that makes it slightly easier. This requires an inline function, which means you'd change your function to:
inline fun <reified T> DataInput.getBookList(): MutableList<T> { // Notice the `<reified T>`
val list = mutableListOf<T>() // Use T here
val size = this.readInt()
for(i in 0 .. size) {
// This is where the initialization happens; you get the constructor, and create a new instance.
// Also works with arguments, if you have any, but you used an empty one so I assume yours is empty
val item = T::class.java.getConstructor().newInstance()!!
item.readExternal(this) // However, this is tricky. See my notes below this code block
list.add(item)
}
return list
}
However, readExternal isn't present in Any, which will present problems. The only exception is if you have an extension function for either Any or a generic type with that name and input.
If it's specific to some classes, then you can't do it like this, unless you have a shared parent. For an instance:
class Book(){
fun readExternal(input: DataInput) { /*Foo bar */}
}
class Person(){
fun readExternal(input: DataInput) { /*Foo bar */}
}
Would not work. There's no shared parent except Any, and Any doesn't have readExternal. The method is manually defined in each of them.
You could create a shared parent, as an interface or abstract class (assuming there isn't one already), and use <reified T : TheSharedParent>, and you would have access to it.
You could of course use reflection, but it's slightly harder, and adds some exceptions you need to handle. I don't recommend doing this; I'd personally use a superclass.
inline fun <reified T> DataInput.getBookList(): MutableList<T> {
val list = mutableListOf<T>()
val size = this.readInt()
val method = try {
T::class.java.getMethod("readExternal", DataInput::class.java)
}catch(e: NoSuchMethodException){
throw RuntimeException()
}catch(e: SecurityException){
throw RuntimeException()// This could be done better; but error handling is up to you, so I'm just making a basic example
// The catch clauses are pretty self-explanatory; if something happens when trying to get the method itself,
// These two catch them
}
for(i in 0 .. size) {
val item: T = T::class.java.getConstructor().newInstance()!!
method.invoke(item, this)
list.add(item)
}
return list
}

obtain class from nested type parameters in kotlin

I have a val built like this
val qs = hashMapOf<KProperty1<ProfileModel.PersonalInfo, *> ,Question>()
How can I obtain the class of ProfileModel.PersonalInfo from this variable?
In other words what expression(involving qs of course) should replace Any so that this test passes.
#Test
fun obtaionResultTypeFromQuestionList(){
val resultType = Any()
assertEquals(ProfileModel.PersonalInfo::class, resultType)
}
Thank you for your attention
There is no straight way to get such information due to Java type erasure.
To be short - all information about generics (in your case) is unavailable at runtime and HashMap<String, String> becomes HashMap.
But if you do some changes on JVM-level, like defining new class, information about actual type parameters is kept. It gives you ability to do some hacks like this:
val toResolve = object : HashMap<KProperty1<ProfileModel.PersonalInfo, *> ,Question>() {
init {
//fill your data here
}
}
val parameterized = toResolve::class.java.genericSuperclass as ParameterizedType
val property = parameterized.actualTypeArguments[0] as ParameterizedType
print(property.actualTypeArguments[0])
prints ProfileModel.PersonalInfo.
Explanation:
We define new anonymous class which impacts JVM-level, not only runtime, so info about generic is left
We get generic supperclass of our new anonymous class instance what results in HashMap< ... , ... >
We get first type which is passed to HashMap generic brackets. It gives us KProperty1< ... , ... >
Do previous step with KProperty1
Kotlin is tied to the JVM type erasure as well as Java does. You can do a code a bit nice by moving creation of hash map to separate function:
inline fun <reified K, reified V> genericHashMapOf(
vararg pairs: Pair<K, V>
): HashMap<K, V> = object : HashMap<K, V>() {
init {
putAll(pairs)
}
}
...
val hashMap = genericHashMapOf(something to something)

Prevent Kotlin from forcing Java to see a wildcard type

This works fine:
class Wrapped<out T>(val value: T)
open class Wrapper<T> {
fun wrap(map: T): Wrapped<T> = Wrapped(map)
}
class Wrapper2 : Wrapper<Map<String, String>>()
val wrapped: Wrapped<Map<String, String>> = Wrapper2().wrap(mapOf())
But, when I try to access Wrapper2.wrap from Java, the Map comes back with a wildcard type:
Map<String, String> toWrap = new HashMap<>();
Wrapped<Map<String, String>> result;
result = new Wrapper<Map<String, String>>().wrap(toWrap); // ok
result = new Wrapper2().wrap(toWrap); // NOT ok, returns Wrapped<Map<String, ? extends String>>
I can work around this by overriding wrap in Wrapper2 with the explicit type.
Why does Wrapper2.wrap return a different type than Wrapper.wrap?
You can suppress Kotlin using wildcards in generics as described in the Kotlin reference where it describes the #JvmSuppressWildcards annotation (or the reverse of that #JvmWildcard annotation).
From the docs:
On the other hand, if we don't need wildcards where they are generated, we can use #JvmSuppressWildcards:
fun unboxBase(box: Box<#JvmSuppressWildcards Base>): Base = box.value
// is translated to
// Base unboxBase(Box<Base> box) { ... }
NOTE: #JvmSuppressWildcards can be used not only on individual type arguments, but on entire declarations, such as functions or classes, causing all wildcards inside them to be suppressed.
Change
class Wrapper2 : Wrapper<Map<String, String>>()
to
class Wrapper2 : Wrapper<MutableMap<String, String>>()
You'll see in the Kotlin source,
public interface Map<K, out V> {
whereas:
public interface MutableMap<K, V> : Map<K, V> {
I believe out V is the reason you're getting ? extends String, see Covariance under the generics docs for Kotlin and a quick search on Google should give you some more insight into covariance and contravariance in Java .

How to get generic param class in Kotlin?

I need to be able to tell the generic type of kotlin collection at runtime. How can I do it?
val list1 = listOf("my", "list")
val list2 = listOf(1, 2, 3)
val list3 = listOf<Double>()
/* ... */
when(list.genericType()) {
is String -> handleString(list)
is Int -> handleInt(list)
is Double -> handleDouble(list)
}
Kotlin generics share Java's characteristic of being erased at compile time, so, at run time, those lists no longer carry the necessary information to do what you're asking. The exception to this is if you write an inline function, using reified types. For example this would work:
inline fun <reified T> handleList(l: List<T>) {
when (T::class) {
Int::class -> handleInt(l)
Double::class -> handleDouble(l)
String::class -> handleString(l)
}
}
fun main() {
handleList(mutableListOf(1,2,3))
}
Inline functions get expanded at every call site, though, and mess with your stack traces, so you should use them sparingly.
Depending on what you're trying to achieve, though, there's some alternatives. You can achieve something similar at the element level with sealed classes:
sealed class ElementType {
class DoubleElement(val x: Double) : ElementType()
class StringElement(val s: String) : ElementType()
class IntElement(val i: Int) : ElementType()
}
fun handleList(l: List<ElementType>) {
l.forEach {
when (it) {
is ElementType.DoubleElement -> handleDouble(it.x)
is ElementType.StringElement -> handleString(it.s)
is ElementType.IntElement -> handleInt(it.i)
}
}
}
You can use inline functions with reified type parameters to do that:
inline fun <reified T : Any> classOfList(list: List<T>) = T::class
(runnable demo, including how to check the type in a when statement)
This solution is limited to the cases where the actual type argument for T is known at compile time, because inline functions are transformed at compile time, and the compiler substitutes their reified type parameters with the real type at each call site.
On JVM, the type arguments of generic classes are erased at runtime, and there is basically no way to retrieve them from an arbitrary List<T> (e.g. a list passed into a non-inline function as List<T> -- T is not known at compile-time for each call and is erased at runtime)
If you need more control over the reified type parameter inside the function, you might find this Q&A useful.