Generics - type mismatch, unable to convert from Java

Hey guys,

I’m having headache with converting Java class to Kotlin. The problem are generics - I’m receiving compilation error “Type mismatch”. I would be grateful for help, I’ve spent a lot of time on research and no progress with the issue. Simplified code looks like:

Java code:

public class Test {

static class Item {}

static class SelectableItem extends Item {}

interface Listener<T extends Item> {
    void onItemClicked(T item);
}

private Listener listener;

public void setListener(Listener listener) {
    this.listener = listener;
}

public void notifyItemClicked(Item item) {
    listener.onItemClicked(item);
}

public static void test() {
    Test test = new Test();
    Listener<SelectableItem> listener = new Listener<SelectableItem>() {
        @Override
        public void onItemClicked(SelectableItem item) {

        }
    };

    test.setListener(listener);
    test.notifyItemClicked(new SelectableItem());
}

}

Compiles and works perfectly fine. So far, this is what I’m getting with Kotlin:

class Test {

open class Item

internal class SelectableItem : Item()

interface Listener<T : Item> {
    fun onItemClicked(item: T)
}

private lateinit var listener: Listener<Item>

fun <T : Item> setListener(listener: Listener<T>) {
    this.listener = listener // DO NOT COMPILE - Type mismatch
}

fun notifyItemClicked(item: Item) {
    listener.onItemClicked(item)
}

companion object {
    fun test() {
        val test = Test()
        val listener: Listener<SelectableItem> = object : Listener<SelectableItem> {
            override fun onItemClicked(item: SelectableItem) {
                
            }
        }
        test.setListener(listener)
        test.notifyItemClicked(SelectableItem())
    }
}

}

Generics are slightly different than in Java, and the Kotlin documentation may be a little complicated to grasp.

In your case, it will depend on what you are going to do.

Something like that would compile but may not allow what you want to do:

class Test {
    open class Item
    internal class SelectableItem : Item() {
        fun select() {

        }
    }

    interface Listener {
        fun onItemClicked(item: Item)
    }

    private var listener: Listener? = null

    fun setListener(listener: Listener) {
        this.listener = listener
    }

    fun <T : Item> notifyItemClicked(item: T) {
        listener?.onItemClicked(item)
    }

    companion object {
        fun test() {
            val test = Test()
            val listener: Listener = object : Listener {
                override fun onItemClicked(item: Item) {

                }
            }
            test.setListener(listener)
            test.notifyItemClicked(SelectableItem())
        }
    }
}

This is another way to do it. I’m not a fan of onItemClicked accepting any type of T though, but I couldn’t find a way to make it accept anything else…

interface Item
class SelectableItem : Item

interface Listener<T> {
    fun <T> onItemClicked(item: T): Any = {}
}

class Test {
    var listener: Listener<out Item> = object : Listener<Item> {

    }

    fun <T : Item> notifyItemClicked(item: T) {
        listener.onItemClicked(item)
    }

    companion object {
        fun test() {
            val test = Test()
            val listener = object : Listener<SelectableItem> {
                override fun <SelectableItem> onItemClicked(item: SelectableItem) {
                    println(item)
                }
            }
            test.listener = listener
            test.notifyItemClicked(SelectableItem())
        }
    }
}

fun main() {
    Test.test()
}

Thank you for your effort, I really appreciate it.

  1. 1st approach sounds like we are giving up generics and simply use base class only. Not what I’m looking for

  2. 2nd approach allows to declare Listener with any T type, what’s more, even mixing classes, e.g.

interface Listener<T : Item> {
    fun <T> onItemClicked(item: T)
}

            val listener = object  : Listener<Item> {
                override fun <Int> onItemClicked(item: Int) {
                    // hey, we are using Int here, only Item shoulde be acceptable !!!
                }
            }

I guess it’s not possible to convert my class from Java to Kotlin properly. I have to find different solution or just stick with Java, because project uses a lot of generics and there is no way to convert them (at least I don’t see any solution)…

Actually that’s what is done in your Java example. You create Listener for a specific type (SelectableItem), but then store it in a Listener variable with no type parameter, so that’s where generics are given up. This Listener is called from notifyItemClicked accepting any Item.
So this way, you may call notifyItemClicked(new Item()), or with any other Item subclass, and your listener, that depends on SelectableItem type may not work.

Kotlin is actually preventing such problem, so that either the Listener.onItemClicked accepts Item and supports all items (1), or the Listener is generic and supports specific subclass of Item, and then needs to be stored in variable of respective type, and be called with parameter of such type, e.g. by making Test class generic as well (2).

Example (1):

class Test {
    open class Item
    class SelectableItem(val style: String) : Item()

    interface Listener {
        fun onItemClicked(item: Item)
    }

    var listener: Listener? = null //Note: you don't need setter, as it is already part of public var property

    fun notifyItemClicked(item: Item) {
        listener?.onItemClicked(item)
    }
}

fun main() {
    val test = Test()
    test.listener = object : Test.Listener {
        override fun onItemClicked(item: Test.Item) {
            // println(item.style) // cannot access, because this method can be called for any Item
            println(item)
        }
    }
    test.notifyItemClicked(Test.Item())
    test.notifyItemClicked(Test.SelectableItem("blue"))
}

example (2)

class Test<T : Test.Item> {
    open class Item
    class SelectableItem(val style: String) : Item()

    interface Listener<T : Item> {
        fun onItemClicked(item: T)
    }

    var listener: Listener<T>? = null //Note: you don't need setter, as it is already part of public var property

    fun notifyItemClicked(item: T) {
        listener?.onItemClicked(item)
    }
}

fun main() {
    val test = Test<Test.SelectableItem>()
    test.listener = object : Test.Listener<Test.SelectableItem> {
        override fun onItemClicked(item: Test.SelectableItem) {
            println(item.style) // we can access it, because we know it's always SelectableItem
        }
    }
    test.notifyItemClicked(Test.SelectableItem("blue")) // works
    //test.notifyItemClicked(Test.Item())         // does not compile - we are safe!
}
2 Likes