7

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) } 
2
  • Is it necessary to have an enum which seems to do nothing more than mirror the class types? You could simply be passing in the type to getMapper rather than trying to match enums to types. Commented Oct 25, 2017 at 17:15
  • it was meant as an example, since in my real case i preferred to have an enum/identifier. But in the end yes, i could pass the type Commented Oct 26, 2017 at 22:15

3 Answers 3

6

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.

Sign up to request clarification or add additional context in comments.

2 Comments

i don't understand why we would need the "as Printer<T>" inside getMapper, but that's ok. Your answer was the best, thanks. I ended up using a solution where i pass in the class type as a getMapper argument. (copied from your link)
In the end this looks like an example for "How to break type safety?".
0

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.

Comments

0

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.

1 Comment

yes, mapToString(5) was a typo. Anyway, i had to fight with the compiler to understand the error messages but now that you say that, it makes sense. thanks for your answer.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.