Muhammad Ahmed AbuTalib
Muhammad Ahmed AbuTalib

Reputation: 4322

Kotlin generics anomaly

Ok, so I have three interface types.

  1. Movement<T : Animal>
  2. Animal with subinterfaces Cat, Dog, Horse
  3. AnimalMovement

Movement Interface

interface  Movement<T : Animal> {
    fun moveAnimal(type:T)
}

Animal Interfaces

interface Animal {
    fun takeSteps()
    fun jump()
    fun hide()
}

interface Cat : Animal
interface Dog : Animal

AnimalMovement

interface CatMovement : Movement<Cat>

I then implement the CatMovement interface

class CatMovementImpl : CatMovement {
    override fun moveAnimal(type: Cat) {
        TODO("not implemented")        
    }
}

Problem

fun TestGenerics() {
    var catMovement : Movement<Cat> = CatMovementImpl() // this works
    var catMovement : Movement<Animal> = CatMovementImpl() // this doesn't?
}

I am sure in Java both lines would have worked fine. However in Kotlin the second line fails to execute. Why would that be? An animal is the base type for cat, so this should have worked right?

Upvotes: 0

Views: 59

Answers (2)

Willi Mentzel
Willi Mentzel

Reputation: 29884

What moskito said in the comments is correct.

I am pretty sure that doesn't work in Java either. A Movement<Cat> is NOT a subtype of Movement<Animal>, the same way a List is NOT a subtype of List<Object>. You might want to read this.

But in Kotlin this is possible using type variance.

fun TestGenerics() {
    var catMovement1: Movement<Cat> = CatMovementImpl()
    var catMovement2: Movement<out Animal> = CatMovementImpl() // works
}

You basically tell the compiler "accept all implementations of Movement<Animal> or implementations Movement<S> for which S has Animal as upper bound".

But, then a problem arises. You cannot invoke

val cat: Cat = /* ... */
catMovement2.moveAnimal(cat) // error

giving you the error

Out-projected type Movement<out Animal> prohibits the use of [...].

because T can only be used as producer (out position) and not as consumer (in position) like this (function made up to demonstrate the point):

val c: Cat = catMovement2.getAnimal() // works

This problem becomes clear right away when you use out at the Movement declaration like this:

interface Movement<out T : Animal> {
    fun moveAnimal(type: T) // error
}

It depends on your use case but maybe you should just let Kotlin infer the type, which would be CatMovementImpl.

var catMovement = CatMovementImpl()

Credit goes to EpicPandaForce for already suggesting using out in the comments.

Upvotes: 1

Benoit
Benoit

Reputation: 5394

I am not an expert of Kotlin, but that seems perfectly normal:

When declared like this:

var animalMovement : Movement<Animal>

You can write code:

animalMovement.moveAnimal(dog)

But if assigning it like this:

var animalMovement : Movement<Animal> = CatMovementImpl()

is allowed, it means that your CatMovementImpl should be able to move a dog ?

Upvotes: 2

Related Questions