How to map a generic class to a generic array of the same class in Kotlin

Viewed 226

I am wondering, how you can map a generic class to a generic array of the same type in Kotlin.

Given following class:

class <T> MyClass(val type : Class<T>) {
  fun new() = type.newInstance()
}

I would like to have something like:

class MyOtherClass {
  val map = Map<Class<T>, MyClass<T>>

  inline fun <reified T> get() : MyClass<T> {
    if(T::class.java in map) {
      return map[T::class.java]
    } else {
      val newInstance = MyClass(T::class.java)
      map[T::class.java] = newInstance
      return newInstance
    }
  }
}

However, this does not work because:

  • T cannot be used in MyOtherClass
  • changing T to Any in MyOtherClass will throw a type inference error that Any is needed but we have T
  • changing to out Any does also not work
  • changing to does work but gives a warning that 'return map[T::class.java]' is an unchecked cast

I don't know how and if you can write that but from a logical point of view I want to achieve the following:

  • have a generic manager class that does certain things for a given type (=MyClass above)
  • have another class that caches such manager classes by their type. If a manager of the given type exists, then it should use that one instead of creating a new one
  • Therefore, I would like to map a type to a manager of the same type

As mentioned, I have one way to write it which would be the following but I would like to avoid the casting warning because in my opinion it is not valid since the map should tell the compilter that getting something of type X will return a MyClass of type X.

class MyOtherClass {
  val map = Map<Class<out Any>, MyClass<out Any>>

  inline fun <reified T:Any> get() : MyClass<T> {
    if(T::class.java in map) {
      return map[T::class.java] as MyClass<T>
    } else {
      val newInstance = MyClass(T::class.java)
      map[T::class.java] = newInstance
      return newInstance
    }
  }
}

Thank you all for your help!

1 Answers

As mentioned by Lino and in the opening post, it is not possible without a warning because the generic information T is lost for Something.

Also, * can be used as a wildcard in Kotlin and therefore following approach is working (taken from the real code):

inline fun <reified T> componentManager(): ComponentManager<T> = componentManager(T::class.java)

    @Suppress("UNCHECKED_CAST")
    fun <T> componentManager(type: Class<T>): ComponentManager<T> {
        return when {
            componentManagers.containsKey(type) -> {
                componentManagers[type] as ComponentManager<T>
            }
            else -> {
                val manager = ComponentManager(initialEntityCapacity, type)
                addListener(manager)
                componentManagers.put(type, manager)
                manager
            }
        }
    }

Thanks to Lino!

Related