Reputation: 193
I'm having some trouble when trying to translate Java code with nested generics to Kotlin. Take this Java SSCCE as an example (please note the relation between S and T):
public class JavaTest {
private class JavaObjectContainer<S> {
public S obj;
}
private abstract class JavaSampleClass<S, T extends JavaObjectContainer<S>> {
private Class<S> type;
public JavaSampleClass(Class<S> type) {
this.type = type;
}
public Class<S> getType() {
return type;
}
public abstract void callMethod(S s);
}
private class JavaChildSampleClass extends JavaSampleClass<String, JavaObjectContainer<String>> {
public JavaChildSampleClass() {
super(String.class);
}
@Override
public void callMethod(String s) {}
}
private class JavaTestContainer {
private Map<Class<?>, JavaSampleClass> sampleClasses;
public JavaTestContainer() {
this.sampleClasses = new HashMap<>();
}
public void registerJavaSampleClass(JavaSampleClass javaSampleClass) {
sampleClasses.put(javaSampleClass.getType(), javaSampleClass);
}
public void callMethod(Object obj) {
sampleClasses.get(obj.getClass()).callMethod(obj);
}
}
public void test() {
JavaTestContainer javaTestContainer = new JavaTestContainer();
javaTestContainer.registerJavaSampleClass(new JavaChildSampleClass());
javaTestContainer.callMethod("Hola");
}
}
Think of this SSCCE as an implementation of a generic factory pattern, where the user registers multiple JavaSampleClass whose methods can be invoked in the future.
As Kotlin does not provide an alternative to wildcards, I have tried the following approach:
class KotlinTest {
private class KotlinObjectContainer<S> {
var obj : S? = null
}
private open class KotlinSampleClass<S, T : KotlinObjectContainer<S>>(var type: Class<S>) {
fun callMethod(s : S) {}
}
private class KotlinChildSampleClass : KotlinSampleClass<String, KotlinObjectContainer<String>>(String::class.java)
private inner class KotlinTestContainer {
private val sampleClasses: MutableMap<Class<Any>, KotlinSampleClass<Any, KotlinObjectContainer<Any>>> = mutableMapOf()
fun registerKotlinSampleClass(kotlinSampleClass: KotlinSampleClass<Any, KotlinObjectContainer<Any>>) {
sampleClasses.put(kotlinSampleClass.type, kotlinSampleClass)
}
fun callMethod(obj : Any) {
sampleClasses[obj.javaClass]?.callMethod(obj)
}
}
fun test() {
val kotlinTestContainer = KotlinTestContainer()
// Exception!
kotlinTestContainer.registerKotlinSampleClass(KotlinChildSampleClass())
kotlinTestContainer.callMethod("Hello")
}
}
The above code throws the following exception in the IDE:
Type mismatch.
Required: KotlinTest.KotlinSampleClass<Any, KotlinObjectContainer<Any>>
Found: KotlinTest.KotlinChildSampleClass
I have been thinking of declaring sampleClasses map as
MutableMap<*, *>
But then, how can I initialize it? Also, as * represents an out-projected parameter, the IDE shows me an error when trying to put new values in the map.
How can I overcome this issue? I'm quite certain that I'm missing something...
Upvotes: 3
Views: 1771
Reputation: 170919
As Kotlin does not provide an alternative to wildcards...
I have been thinking of declaring sampleClasses map as
MutableMap<*, *>
For this case *
corresponds to wildcards perfectly well. If you have Class<?>
in Java, you want Class<*>
in Kotlin, not Class<Any>
or *
.
private val sampleClasses: MutableMap<Class<*>, KotlinSampleClass<*, *>> = mutableMapOf()
fun registerKotlinSampleClass(kotlinSampleClass: KotlinSampleClass<*, *>) {
sampleClasses.put(kotlinSampleClass.type, kotlinSampleClass)
}
@Suppress("UNCHECKED_CAST")
fun callMethod(obj : Any) {
(sampleClasses[obj.javaClass] as KotlinSampleClass<Any, *>?)?.callMethod(obj)
}
The only reason you don't need the cast in callMethod
in Java is because you are using raw types (as Turing85's comment mentions) and the compiler basically gives up on type checking.
Upvotes: 1