How do I create a "Class" instance in Kotlin with specific type parameters? - kotlin

I have this parent class and this child class:
abstract class ParentClass<T> {
protected abstract val dataClass: Class<T>
}
class ChildClass<K, V> : ParentClass<Map<K, V>>() {
override val dataClass: Class<Map<K, V>>
get() = HashMap::class.java // compilation error, invalid return type
}
I want the child class to implement the dataClass member such that I can use it to create instances of type T later on:
T data = dataClass.newInstance()
However, the return type for my implementation of dataClass is not valid in this context, so it doesn't compile:
Class<Map<K, V>> // required
Class<HashMap<*, *>> // found
Is there any way to specify the type parameters for the Class instance, or a different solution altogether to this problem? I realize I could replace the parent member with Class<*>, but I want something that is type safe.

I just ended up modifying the member into a function to generate a new instance, and added a second lazy member to get the class by creating a temporary instance:
abstract class ParentClass<T : Any> {
protected abstract fun createDataInstance(): T
private val dataClass: Class<T> by lazy {
createDataInstance()::class.java as Class<T>
}
}
class ChildClass<K, V> : ParentClass<Map<K, V>>() {
override fun createDataInstance(): Map<K, V> = HashMap()
}
// snippet (Java)
ChildClass<Integer, Integer> childClass = new ChildClass<>();
Map<Integer, Integer> instance = childClass.getDataInstance();
instance.put(0, 1);
System.out.println("Child instance #1: " + instance);
System.out.println("Child instance class " + childClass.getDataClass());
System.out.println("Child instance #2: " + childClass.getDataInstance());
// output (Java)
Child instance #1: {0=1}
Child instance class class java.util.HashMap
Child instance #2: {}

Related

How to call an abstract method from a Class parameter in Kotlin?

Aim
Have a function Book, which takes one of three Letter classes as argument myClass and then calls 'genericMethod()' from the abstract class which Letter*() has inherited.
Issue
If I try Book(LetterA()).read() I get the following error:
Type mismatch. Required: Class<SampleClassArguments.Alphabet> Found: SampleClassArguments.LetterA
Does Kotlin have any way to achieve this result?
Code
#Test
fun readBookTest() {
Book(LetterA()).read() /*<--error here*/
}
class Book(val myClass: Class<Alphabet>) {
fun read() {
val letterClass = myClass.getConstructor().newInstance()
letterClass.genericMethod(myClass.name)
}
}
class LetterA(): Alphabet()
class LetterB(): Alphabet()
class LetterC(): Alphabet()
abstract class Alphabet {
fun genericMethod(className: String) {
println("The class is: $className")
}
}
You need to define the Class type as covariant with the out keyword so any of the child classes is an acceptable argument:
class Book(val myClass: Class<out Alphabet>)
And when you use it, you need to pass the actual Class, not an instance of the class. You can get the Class by calling ::class.java on the name of the class:
#Test
fun readBookTest() {
Book(LetterA::class.java).read()
}

How to get a type parameter's class name?

I have a base class:
abstract class JSONDeserializationStrategy<T : Any>: DeserializationStrategy<T> {
protected abstract fun parse(json: JsonObject): T
protected abstract fun getSerializationException(): SerializationException
}
and then a derived class
class MyClassParserDeserializationStrategy : JSONDeserializationStrategy<MyClass>() {
override val descriptor: SerialDescriptor
= buildClassSerialDescriptor("MyClass")
override fun getSerializationException(): SerializationException
= throw SerializationException("Invalid JSON received for MyClass.")
How could I move the property descriptor and the method getSerializationException from the derived class into the base class, since they only "adapt" by providing their name as String? I was trying to do something in the direction of T::class.java.simpleName as String but it didnt work. What is the best way to do this?
As #Tenfour04 explained, T is erased, so it is not directly accessible. However, as long as the subclass of JSONDeserializationStrategy provides the T as a specific class/type, it can be acquired with a bit of reflection voodoo:
fun main() {
val strategy = MyClassParserDeserializationStrategy()
println(strategy.descriptor.serialName) // MyClass
}
abstract class JSONDeserializationStrategy<T : Any>: DeserializationStrategy<T> {
protected val type: KType = this::class.supertypes
.first { it.classifier == JSONDeserializationStrategy::class }
.arguments[0].type!!
#Suppress("UNCHECKED_CAST")
protected val typeClass = requireNotNull(type.classifier as? KClass<T>) {
"T is unknown"
}
override val descriptor: SerialDescriptor = buildClassSerialDescriptor(typeClass.simpleName!!)
fun getSerializationException(): SerializationException =
throw SerializationException("Invalid JSON received for ${typeClass.simpleName!!}.")
...
}
I'm not 100% sure, but I believe both type!! and simpleName!! are safe, they can't be null.
Also, it won't work if T is really fully erased and unknown, e.g.:
val strategy = GenericParserDeserializationStrategy<MyClass>() // exception
For this reason it makes sense to open type/typeClass properties for overriding, so generic non-abstract subclasses could provide their own means to acquire T. However, then we would probably need to move the initialization of most of properties outside of the constructor.
Because of type erasure, T's class is not accessible. Work-around could be to add it as a constructor property that returns the type, and then the subclasses must pass the type. The property needs to be in the constructor, rather than provided as an abstract property for subclasses to override because you need it to initialize descriptor at instantiation time. (It's highly discouraged to call an open property at class initialization time.)
abstract class JSONDeserializationStrategy<T : Any>(protected val typeClass: KClass<out T>): DeserializationStrategy<T> {
protected abstract fun parse(json: JsonObject): T
override val descriptor: SerialDescriptor = buildClassSerialDescriptor(typeClass.simpleName!!)
fun getSerializationException(): SerializationException =
throw SerializationException("Invalid JSON received for ${typeClass.simpleName}.")
}
class TodaySaleParserDeserializationStrategy : JSONDeserializationStrategy<TodaySale>(TodaySale::class) {
}

Calling an overloaded method with the base class parameter type

Is it possible in Kotlin to call an overloaded method using the base class type as a parameter? This is best illustrated via an example
Base Sealed Class + Derived Classes
sealed class Event {
abstract val eventId: String
}
data class FirstEvent(
override val eventId: String
val first: String
) : Event()
data class SecondEvent(
override val eventId: String
val second: String
) : Event()
Utility Class having an overloaded method for each of the derived classes
class UtilityClass {
fun handle(event: FirstEvent) {
....
}
fun handle(event: SecondEvent) {
....
}
}
Is it possible to call methods of the utility class in such a way utility.handle(FirstEvent("id", "first) as Event) doing so is giving me the following exception
None of the following functions can be called with the arguments supplied.
you can do something like this
fun handleEvent(event: Event) {
when (event) {
is FirstEvent -> {
// event is automatically casted as FirstEvent
event.first
}
is SecondEvent -> ...
}
}

Kotlin generics with in produces Type mismatch when compiling

I´m working on a code with generics and when I use an in I got a TypeMismatch when compiling.
The code is the following:
open class A
class B:A()
data class DataContainer(val a:String,
val b:A)
interface Repo<T:A>{
fun setParam(param:T)
fun getParam():T
}
abstract class RepoImp<T:A>:Repo<T>{
private lateinit var parameter:T
override fun setParam(param: T) {
parameter = param
}
override fun getParam(): T {
return parameter
}
}
class BRepo:RepoImp<B>()
class Repo2(val repo: Repo<in A>){
fun process(b:DataContainer){
repo.setParam(b.b)
}
}
val repoB = BRepo()
val repo2 = Repo2(repoB)// Here I got: Type mismatch: inferred type is BRepo but Repo<in A> was expected
I also tried changing the attribute repo from Repo2 to Repo<*>
Since BRepo is a Repo<B>, it is not a Repo<in A>, (but it would satisfy Repo<out A>).
In other words, a Repo<in A> must be able to accept setParam(A()), but BRepo.setParam() can only accept a B or subclass of B.
Or to put it another way, BRepo is a Repo<B>, which is a tighter restriction on the type than Repo<A> when it comes to writing values (but looser restriction when reading values).
The reason class Repo2(val repo: Repo<*>) doesn't work is that Repo<*> is essentially a Repo<in Nothing/out A>. You can't call setParam() on a Repo<*> with any kind of object.
There's a design flaw in your code that you can't fix simply by changing Repo2's constructor signature. As it stands now, Repo2 needs to be able write A's to the object you pass to it, and a BRepo by definition does not support writing A's, only B's. You will need to make at least one of your class's definitions more flexible about types.
It might be easier to understand the covariance limitation with more common classes:
val stringList: MutableList<String> = ArrayList()
var anyList: MutableList<in Any> = ArrayList()
anyList.add(5) // ok
anyList = stringList // Compiler error.
// You wouldn't be able to call add(5) on an ArrayList<String>
Basically MutableList<String> is not a MutableList<in Any> the same way Repo<B> is not a Repo<in A>.
The Repo2 class expect to consume only type A, use Repo2<T : A>(val repo: Repo<in T>)
open class A
class B : A()
class C : A()
class D : A()
class BRepo : RepoImp<B>()
class CRepo : RepoImp<C>()
class DRepo : RepoImp<D>()
interface Repo<T : A> {
fun setParam(param: T)
fun getParam(): T
}
abstract class RepoImp<T : A> : Repo<T> {
private lateinit var parameter: T
override fun setParam(param: T) {
parameter = param
}
override fun getParam(): T {
return parameter
}
}
class Repo2<T : A>(val repo: Repo<in T>) {
fun process(b: DataContainer<T>) {
repo.setParam(b.b)
}
}
data class DataContainer<T : A>(
val a: String,
val b: T
)
fun main() {
val repoB = BRepo()
val repoC = CRepo()
val repoD = DRepo()
val repo2 = Repo2(repoB)
val repo3 = Repo2(repoC)
val repo4 = Repo2(repoD)
repo2.process(DataContainer("Process B type", B()))
repo3.process(DataContainer("Process C type", C()))
repo4.process(DataContainer("Process D type", D()))
println(repo2.repo.getParam())
println(repo3.repo.getParam())
println(repo4.repo.getParam())
}

Kotlin: platform declaration clash: same JVM signature

I'm trying to implement an abstract class in kotlin which extends a MultiValuedMap, when I was trying to override keySet() method, I got the error
platform declaration clash: The following declarations have the same JVM signature (keySet()Ljava/util/Set;)
My code:
abstract class ConfigProperties<K, V>(delegate: Map<K, V>?): MultivaluedMap<String, String> {
protected val delegate: Map<K, V>
init {
if (delegate == null) {
throw NullPointerException("Config properties delegate must not be null.")
}
this.delegate = delegate
}
abstract fun putCacheProperty(key: Parameter, value: Any)
abstract fun getCacheProperty(key: Parameter): Any
protected val UNSUPPORTED_MESSAGE = "ConfigProperties is immutable."
override fun keySet(): Set<String> {
return delegate.keys
}
}
Any hint to solve this? Thanks!
I think your problem begins with MultivaluedMap<String,String>
abstract class ConfigProperties<K, V>(delegate: Map<K, V>?):
MultivaluedMap<String, String> { ... }
Overlook the String type parameter for the moment. MultivaluedMap<K,V> is an interface that has the Map<K,List<V>> super interface. But in your code, you have a delegate of type Map<K,V>. You try to override a the setKey member of the Map<K,List<V>> super interface by returning delegate.keys which is not the same as Map<K,List<V>>.keys (i.e., whose member you are overriding).
So, you can try the following...
abstract class ConfigProperties<K, V>(delegate: Map<K, V>?):
MultivaluedMap<K, V> {
protected val delegate: Map<K, List<V>>
init {
if (delegate == null) {
throw NullPointerException("Config properties delegate must not be null.")
}
this.delegate = delegate
}
abstract fun putCacheProperty(key: Parameter, value: Any)
abstract fun getCacheProperty(key: Parameter): Any
protected val UNSUPPORTED_MESSAGE = "ConfigProperties is immutable."
override fun keySet(): Set<K> {
return delegate.keys
}
}
As for the String type parameter, did you mean to use K,V? Whatever you meant, you will need to make the consistent.