3

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...

UnaDeKalamares
  • 193
  • 2
  • 12
  • Possibly unrelated, but still a code smell: [Don't use raw types](https://stackoverflow.com/questions/2770321/what-is-a-raw-type-and-why-shouldnt-we-use-it). – Turing85 Apr 16 '18 at 15:13
  • If I'm not mistaken, using raw types is basically what makes this approach so powerful in Java. I've implemented somethind along this lines for a multi-type RecyclerView.Adapter. It's just a matter of being cautious, and adding a layer of protection ensuring that you are trying to access a factory which was already added. Anyway, I'll study your observation, thanks! – UnaDeKalamares Apr 17 '18 at 06:23

1 Answers1

1

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.

Community
  • 1
  • 1
Alexey Romanov
  • 167,066
  • 35
  • 309
  • 487
  • Thank you very much! You hit the nail right in the head. I'll do my best in order to refactor my code to avoid raw types if necessary (and understanding how to notice them in Kotlin) – UnaDeKalamares Apr 17 '18 at 06:37
  • 1
    You don't need to notice them in Kotlin because Kotlin doesn't support them. The hard part is noticing them in Java. – Alexey Romanov Apr 17 '18 at 06:45