Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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)
}
like image 871
Ricardo Belchior Avatar asked Oct 25 '17 17:10

Ricardo Belchior


People also ask

How do I find my generic type Kotlin?

There are no direct ways to do this in Kotlin. In order to check the generic type, we need to create an instance of the generic class<T> and then we can compare the same with our class.

Can a generic be an interface?

Generics make a class, interface and, method, consider all (reference) types that are given dynamically as parameters. This ensures type safety. Generic class parameters are specified in angle brackets “<>” after the class name as of the instance variable. Generic constructors are the same as generic methods.

Can interface inherit another interface Kotlin?

Kotlin allows Interface to have code which means a class can implement an Interface, and inherit the behavior from it. After using Kotlin in Android development for a while, I've just realized the benefit of Interface in Kotlin. In Java 6, Interface can only be used to describe the behaviors, but not implement them.

How to get the value of a generic type in Kotlin?

To achieve that using Kotlin, we need to use the out keyword on the generic type. It means that we can assign this reference to any of its supertypes. The out value can be only be produced by the given class but not consumed: We defined a ParameterizedProducer class that can produce a value of type T.

How to create an instance of a class using Kotlin's type system?

To create an instance of such a class, simply provide the type arguments: But if the parameters can be inferred, for example, from the constructor arguments, you can omit the type arguments: One of the trickiest aspects of Java's type system is the wildcard types (see Java Generics FAQ ). Kotlin doesn't have these.

How to create a parameterized class in Kotlin?

Let’s say that we want to create a parameterized class. We can easily do this in Kotlin language by using generic types: We can create an instance of such a class by setting a parameterized type explicitly when using the constructor: Happily, Kotlin can infer the generic type from the parameter type so we can omit that when using the constructor:

What is an interface in Kotlin?

Kotlin interfaces are similar to interfaces in Java 8. They can contain definitions of abstract methods as well as implementations of non-abstract methods. However, they cannot contain any state. Meaning, interface may have property but it needs to be abstract or has to provide accessor implementations.


1 Answers

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.

like image 83
s1m0nw1 Avatar answered Oct 14 '22 11:10

s1m0nw1