0

I'm doing the code migration from java to kotlin and I have some problem with generics.

This is an example explaining my doubts:

import java.util.*
import kotlin.collections.HashMap

interface MyGenerics<T>{
  fun produce():T
  fun publish(toPublish:T):String
}

class MyImplementation:MyGenerics<Boolean>{

  override fun produce(): Boolean {
    return true
  }

  override fun publish(toPublish: Boolean): String {
    return "test"
  }

}

class RegistrationClass{

  private val workers = HashMap<String, Vector<MyGenerics<Any>>>()

  fun register(type: String, worker: MyGenerics<Any>) {
    if (!workers.containsKey(type)) workers[type] = Vector()
      workers[type]!!.add(worker)


   //usage example
   for (workers in workers[type]!!){
     val product = workers.produce()
     workers.publish(product)
    }
  }

}

fun main() {
   val registrationClass = RegistrationClass()
   registrationClass.register("MyTest", MyImplementation()) //error kotlin implementation

   val javaRegistrationClass = JavaRegistrationClass()
   javaRegistrationClass.register("MyNewTest", MyImplementation()) //java implementation

}

Why I'm getting error on

> registrationClass.register("MyTest", MyImplementation()) //error
> kotlin implementation

and not on:

javaRegistrationClass.register("MyNewTest", MyImplementation()) //java implementation

This is the JavaRestristrationClass:

class JavaRegistrationClass{

    private HashMap workers = new HashMap<String, Vector<MyOtherTest>>();

    public void register(String type, MyGenerics worker) {
        //do something
    }

}

I have read this https://stackoverflow.com/a/65534086/3681634 answer, but I continue to not understand

aeroxr1
  • 1,014
  • 1
  • 14
  • 36
  • 1
    register takes a `MyGenerics` you can't pass a `MyGenerics` to it. In java you're using raw types which kotlin doesn't support. Your code isn't typesafe in Java, so you'll need to change something to make it work in Kotlin. Without seeing the rest of your code it's hard to advice on that. – al3c Mar 15 '21 at 11:17
  • Hi ! Thanks ;) I have edited the question with an usage example. Something like this: /*usage example*/ for (workers in workers[type]!!){ val product = workers.produce() workers.publish(product) } – aeroxr1 Mar 15 '21 at 11:39

2 Answers2

1

In java code you use raw type (MyGenerics), which is unsafe (and therefore was not added to Kotlin) and still present in java just for backwards-compatibility with code written before Java 5.

MyGenerics<Any> type you use instead, is not its equivalent. You need to either provide fair generic type MyGenerics<T> (and make your method generic), or use MyGenerics<*> type.

Since you deliberately mix elements of different types in one generic collection, unchecked casts is unavoidable in all cases.

Option 1 (with MyGenerics<T>):

class RegistrationClass {
    private val workers = HashMap<String, Vector<MyGenerics<Any>>>()

    fun <T> register(type: String, worker: MyGenerics<T>) {
        val workersOfType = workers.getOrPut(type) { Vector() }
        @Suppress("UNCHECKED_CAST")
        workersOfType.add(worker as MyGenerics<Any>)

        //usage example
        workersOfType.forEach { it.publish(it.produce()) }
    }
}

Option 2 (with MyGenerics<*>):

class RegistrationClass {
    private val workers = HashMap<String, Vector<MyGenerics<Any>>>()

    fun register(type: String, worker: MyGenerics<*>) {
        val workersOfType = workers.getOrPut(type) { Vector() }
        @Suppress("UNCHECKED_CAST")
        workersOfType.add(worker as MyGenerics<Any>)

        //usage example
        workersOfType.forEach { it.publish(it.produce()) }
    }
}

Also you may change type of your workers collection (as @Tenfour04 suggests in comments):

Options 1.1/2.1 (with MyGenerics<T>/MyGenerics<*> and Vector<MyGenerics<*>>):

class RegistrationClass {
    private val workers = HashMap<String, Vector<MyGenerics<*>>>()

    fun <T> register(type: String, worker: MyGenerics<T>) { 
//  fun register(type: String, worker: MyGenerics<*>) { //also possible
        val workersOfType = workers.getOrPut(type) { Vector() }
        workersOfType.add(worker)

        //usage example
        workersOfType.forEach {
            @Suppress("UNCHECKED_CAST")
            it as MyGenerics<Any>
            it.publish(it.produce())
        }
    }
}

UPDATE

Actually, it's possible to avoid casting with Options 1.1/2.1, if you can hide operations, revealing type of generic collection elements inside the interface (as @al3c suggests in comments):

interface MyGenerics<T> {
    fun produce(): T
    fun publish(toPublish: T): String
    fun produceAndPublish() = publish(produce())
}

class RegistrationClass {
    private val workers = HashMap<String, Vector<MyGenerics<*>>>()

    fun <T> register(type: String, worker: MyGenerics<T>) { 
//  fun register(type: String, worker: MyGenerics<*>) { //also possible
        val workersOfType = workers.getOrPut(type) { Vector() }
        workersOfType.add(worker)

        //usage example
        workersOfType.forEach { it.produceAndPublish() }
    }
}
  • An explanation of why this works would improve this answer. – matt freake Mar 15 '21 at 13:49
  • 2
    It would be better to use `Vector>>` to be clear about the lack of type safety and force you to consider whether casting is safe where you retrieve items. And it would be closer to the raw types usage in the Java version. – Tenfour04 Mar 15 '21 at 14:27
  • Thanks ! Is there a way to not use the unchecked_cast ? – aeroxr1 Mar 15 '21 at 16:33
  • @Tenfour04, `MyGenerics<*>` is still not equivalent of java raw type. It's equivalent of `MyGenerics>`. I believe this type doesn't mean lack of type safety, but (as well as a `MyGenerics`) reduction to some very basic generic type. The difference is that `MyGenerics<*>` is a supertype of all kinds of `MyGenerics<...>`, and `MyGenerics` is not. So, this implies different limitations on addition/retrieval operations for respectful generic collections. Since in this case there are both kinds of these operations, it just moves unchecked cast to other place, keeping semantics the same. – Михаил Нафталь Mar 16 '21 at 08:37
  • Updated my answer with more comprehensive rationale and alternatives, inspired by @Tenfour04 – Михаил Нафталь Mar 16 '21 at 08:42
  • @aeroxr1, updated my answer with example avoiding unchecked cast. Althought, unchecked cast isn't unsafe in this case, it just relaxes type system to fulfill your needs. – Михаил Нафталь Mar 16 '21 at 10:51
0

I've done in this way:

class RegistrationClass{

  private val bookOrder = HashMap<String, Vector<MyGenerics<*>>>()

  fun register(type: String, worker: MyGenerics<*>) {
    if (!bookOrder.containsKey(type)) bookOrder[type] = Vector()
    bookOrder[type]!!.add(worker)
  }

  fun runExample(type:String){
    //usage example
    for (workers in bookOrder[type]!!){
      val product = workers.produce()
      @Suppress("UNCHECKED_CAST")
      (workers as MyGenerics<Any?>).publish(product)
    }
  }

}

fun main() {
  val registrationClass = RegistrationClass()
  registrationClass.register("MyTest", MyImplementation())
}

but i'm wondering: is there a way to not use the unchecked cast?

aeroxr1
  • 1,014
  • 1
  • 14
  • 36
  • Your code is fundamentally unsafe, the HashMap can't have any idea of that type it holds, since they're all different classes. The best thing would be to remove the parameter from the interface at all, and provide a method which does `worker.publish(worker.produce())` – al3c Mar 15 '21 at 18:37
  • I have created the interface MyGenerics to force the output of method "produce" to be the same type of input of method "publish". Is it the wrong way ? – aeroxr1 Mar 15 '21 at 18:50
  • If you put them all it a map like that, you're not gaining anything. Java doesn't know what type anyway, Java doesn't have the explicit warning, but fundamentally you're doing an unchecked cast as well. For example you could call produce on one worker and pass its output to any other worker, Java won't stop you. If you just pass produce result to publish, then you don't need the generic type. – al3c Mar 15 '21 at 18:52
  • This is a simplification of my real code, this RegistrationClass can accept only class that extends MyGenerycs. The only things that does this RegistrationClass is execute the produce method in background and publish that result on mainthread. – aeroxr1 Mar 15 '21 at 18:59