Kotlin Generics interop with java


#1

I’m having some issues getting generics to work well between Kotlin and Java. I think the root of the issue is my interface both produces and consumes the same generic type. It looks like this:

/**
 * Transforms messages concurrently and consumes them by batches in order
 */
interface MessageConsumer<T> {

    /**
     * transform a single message
    */
    fun transform(message: Message): T

    /**
    * consume a batch of messages in order
    */
    fun consume(messages: List<T>)
}

This works great when implementing in Kotlin, but when implementing in java the type of Batched consume is List<? extends T>. However, I know for that the type can only be List and its a annoying to force users to cast down to the right type. For Example:

public class MyConsumer
    implements MessageConsumer<MyMessage> {

    @Override
    public MyMessage transform(final Message message) {
        return new MyMessage(message);
    }

    @Override
    public void consume(final List<? extends MyMessage> messages) {
         final List<MyMessage> rightType = (List<MyMessage>) messages;
    }
}

So, to work around this I moved the interface in Java.

public interface MessageConsumer<T> {

    T transform(Message message);

    void consume(List<T> messages);
}

So now Java implementations have the right type in consume(List<T> messages). Unfortunate that I had to use Java, but its not much code so whatever.

But, I also want to provide out of the box support for consumes that don’t want to deal with batches, so I have this Kotlin class:

abstract class SingleMessageConsumer<T> : MessageConsumer<T> {

    abstract fun consume(message: T)

    final override fun consume(messages: List<T>) {
        messages.forEach { consume(it) }
    }
}

But again I’ve run into issues with generics in Java. I have the following implementation which does not compile because of: MySingleConsumer is not abstract and does not override abstract method consume(List<MyMessage>) in MessageConsumer.

class MySingleConsumer
    extends SingleMessageConsumer<MyMessage> {

    @Override
    public MyMessage transform(final Message message) {
        return new MyMessage(message);
    }

    @Override
    public void consume(final MyMessage message) {
         // do stuff
    }
}

Even if I implement consume(List<MyMessage>) in MySingleConsumer (which defeats the whole purpose of SingleMessageConsumer) I still get error: name clash: consumed(List<MyMessage>) in MySingleConsumer and consume(List<? extends T>) in SingleMessageConsumer have the same erasure, yet neither overrides the other public void consume(final List<MyMessage> messages).

I could also move SingleMessageConsumer into Java, but thats even more Java code in what was original a purge Kotlin project. Or I can move MessageConsumer back to Kotlin which will fix MySingleConsumer, but I’m back to batch consumers having awkward generics.

Maybe I can structure my code in a different way to get the types to work correctly? Or is there a Kotlin annotation to make these generics work better with java?


#2

I guess you are looking for @JvmSuppressWildcards see here.

You can use it like this

fun consume(messages: List<@JvmSuppressWildcards T>)

#3

Thanks! That is exactly what I was looking for. Not sure how I missed it in the doc. Here is what I have now, which is working well for me. Needed to propagate @JvmSuppressWildcards to all the Kotlin overrides of consume.

interface MessageConsumer<T> {

    fun transform(message: Message): T

    fun consume(messages: List<@JvmSuppressWildcards T>)
}

abstract class SingleMessageConsumer<T> : MessageConsumer<T> {

    abstract fun consume(message: T)

    final override fun consume(messages: List<@JvmSuppressWildcards T>) {
        messages.forEach { consume(it) }
    }
}

#4

Well I knew it existed and it still took me some time to find it. I might create a pull request to add it to the generics doc page so it won’t be just in the java interop part.