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?