Jeff
Jeff

Reputation: 141

Kotlin Factory Class with Generic outputs

I'm in the process of trying to port some code I wrote in Java over to Kotlin and I'm struggling mightily with some issues around generics. I quite commonly use a factory pattern in Java to return an instance of a generic interface that I want to call for a given type.

In Java I had this contract:

public Message<T extends Action> {
    private List<T> actions;

    ..some other properties
}

And this interface:

public interface MessageConverter<T extends Action, M extends BaseModel> {
    List<M> convertMessage(Message<T> message);

    DataType getDataType();
}

And lastly this factory:

public class MessageConverterFactory {
    //This gets populated via DI
    private Map<DataType, MessageConverter> converterMap;

    public <T extends Action, M extends BaseModel> MessageConverter<T, M> getMessageConverter(DataType dataType) {
        return converterMap.get(dataType);
    }
}

With all that in place, I was able to do things like this:

Message<T> message = mapper.readValue(messageString, type);

MessageConverter<T, M> messageConverter = messageConverterFactory.getMessageConverter(dataType);

List<M> dataModels = messageConverter.convertMessage(message);

I understand that I was abusing raw generic types in Java to an extent to make this happen, but I assumed there would be some way to still do a generic factory pattern like this.

However, no matter with I try with generic variance, star projections, etc. I cannot get Kotlin to accept any version of this code. The closest I got was down to the invocation of the generic converter's convertMessage call. It was failing because I was using star projections and attempting to restrict the type of T, but that was leading to the compiler thinking convertMessage accepts Message<Nothing>.

Is code like this possible in Kotlin? Or is there a similar alternative approach I should be using instead?

Thanks, Jeff

Upvotes: 1

Views: 1691

Answers (1)

Raman
Raman

Reputation: 19675

The literal conversion of this to Kotlin is pretty simple, and the Java-to-Kotlin converter built in to IDEA would spit something like this out almost directly, given the equivalent Java code:

class Message<T: Action> {
  private val actions: List<T> = TODO()
  ...
}

interface MessageConverter<T: Action, out M: BaseModel> {
  fun convertMessage(message: Message<T>): List<M>

  val dataType: DataType
}

class MessageConverterFactory(val converterMap: Map<DataType, MessageConverter<*, *>>) {
  fun <T: Action, M: BaseModel> getMessageConverter(dataType: DataType): MessageConverter<T, M> {
    return converterMap[dataType] as MessageConverter<T, M>
  }
}

Note, the cast in getMessageConverter -- your Java code is doing the equivalent, without being explicit about it -- I believe the compiler would even spit out a warning about an unchecked assignment.

An alternative in Kotlin is to use an inline function with reified types to return the appropriate converter. For example, something like this:

inline fun <reified T: Action, reified M: BaseModel> converterOf(): MessageConverter<T, M> = when {
  T::class == FooAction::class, M::class == BarModel::class -> TODO()
  else -> error("No converter available for type ${T::class.simpleName} to ${M::class.simpleName}")
}

Upvotes: 3

Related Questions