Ricardo Belchior
Ricardo Belchior

Reputation: 576

Kotlin: return a generic interface

In Kotlin, I'm trying to compile the following:

  1. given an interface with generic types (Printer)
  2. and two implementations of that interface (DogPrinter and CatPrinter)
  3. return one of the printers, according to an external variable (AnimalType)

The following code does not compile, because a type is required at: fun getMapper(animalType: AnimalType): Printer

I tried to use <Any> or <*> but got no success. Can someone help?

(easy to see the error by copypasting the code below into https://try.kotlinlang.org)

enum class AnimalType {
    CAT, DOG
}

class Dog
class Cat

interface Printer<in T> {
    fun mapToString(input: T): String
}


class DogPrinter : Printer<Dog> {
    override fun mapToString(input: Dog): String {
        return "dog"
    }
}

class CatPrinter : Printer<Cat> {
    override fun mapToString(input: Cat): String {
        return "cat"
    }
}

private fun getMapper(animalType: AnimalType): Printer {
    return when(animalType) {
        AnimalType.CAT -> CatPrinter()
        AnimalType.DOG -> DogPrinter()
    }
}

fun usage_does_not_compile() {
    getMapper(AnimalType.DOG)
            .mapToString(5)
}

Upvotes: 7

Views: 15031

Answers (3)

s1m0nw1
s1m0nw1

Reputation: 81929

I've modified your code a bit. The getMapper function is inline with a reified generic type now, which makes the call pretty readable at getMapper<Dog>().

Read about reified here: What does the reified keyword in Kotlin really do?

private inline fun <reified T> getMapper(): Printer<T> {
    when (T::class) {
        Cat::class -> return CatPrinter() as Printer<T>
        Dog::class -> return DogPrinter() as Printer<T>
    }
    throw IllegalArgumentException()
}

fun main(args: Array<String>) {
    println(getMapper<Dog>().mapToString(Dog()))
    println(getMapper<Cat>().mapToString(Cat()))
}

The reified thing is not even necessary actually but makes the client side more readable. Alternatively, you could also pass in the class as an argument to the getMapper function. The really important part is to make this one generic. The unchecked casts are not very cool but seem to be safe here.

Upvotes: 6

Alexey Romanov
Alexey Romanov

Reputation: 170745

Returning Printer<*> works fine. But it isn't actually useful because you can't call mapToString(Dog()) (I assume mapToString(5) is a typo) and it's a good thing: if it compiled then

getMapper(AnimalType.CAT).mapToString(Dog())

would also have to compile, because the return type can only depend on argument type, not on value, and AnimalType.CAT and AnimalType.DOG have the same type. There are some languages which allow it, but Kotlin isn't one of them.

Upvotes: 0

Ruckus T-Boom
Ruckus T-Boom

Reputation: 4796

Kotlin doesn't have raw types so you can't return Printer from getMapper. You can either return Printer<*>, make getMapper generic, or change your inheritance structure to have a common super type.

Upvotes: 0

Related Questions