1
interface Foo<T: Bar> {
    fun example(bar: T)
}

interface Bar

class Bar1 : Bar

class Bar2 : Bar

class FooEx1 : Foo<Bar1> {
    override fun example(bar: Bar1) { }
}

class FooEx2 : Foo<Bar2> {
    override fun example(bar: Bar2) { }
}

// Won't compile
// Even though FooEx1 and FooEx2 *are* Foo<Bar>
class ExampleDoesntCompile {
    val collection = mutableListOf<Foo<Bar>>().apply {
        this.add(FooEx1())
        this.add(FooEx2())
    }
}

// Will compile
// But have to cast FooEx1 and FooEx2 to Foo<Bar>
class ExampleDoesCompileButRequiresCast {
    val collection = mutableListOf<Foo<Bar>>().apply {
        this.add(FooEx1() as Foo<Bar>)
        this.add(FooEx2() as Foo<Bar>)
    }
}

So, I could for instance, state that Foo's parameterized type is out, but then I get a compile error for the function example:

interface Foo<out T: Bar> {
    fun example(bar: T)
}

Error: Type parameter T is declared as 'out' but occurs in 'in' position in type T

Thomas Cook
  • 4,371
  • 2
  • 25
  • 42
  • A`Foo` is **not** a `Foo`, see this related question (Java): [Is List a subclass of List?](https://stackoverflow.com/questions/2745265/is-listdog-a-subclass-of-listanimal-why-are-java-generics-not-implicitly-po?rq=1) – Jesper Jun 09 '20 at 14:59
  • So it'll crash at run time with the cast? – Thomas Cook Jun 09 '20 at 15:02
  • 1
    It won't crash when you cast it because generic types cannot be checked. It will crash if you retrieve something from this list and try to call `example()` on it by passing a `Bar` to it that the implementation is not equipped to handle. https://pl.kotl.in/h3lGqgowk – Tenfour04 Jun 09 '20 at 15:19
  • 2
    This is what the compiler errors are trying to protect you from when you improperly handle variance/invariance. Your `FooEx1` is a limited implementation of `Foo` that can only handle a specific type of `Bar`. If you could just put it in a list of `Foo`, then the compiler would be allowing you to ignore your own generic type restrictions and opening you up to bugs that are hard to track down. – Tenfour04 Jun 09 '20 at 15:26
  • Sort of makes sense, always makes my brain hurt though. I'm sure I never had these issues when dealing with C# generics. – Thomas Cook Jun 09 '20 at 16:06
  • 1
    Maybe just because contravariant classes aren't as common as covariant/invariant ones? Covariance works like what you were expecting above. C# also has covariance and contravariance, also specified with `out` and `in`. – Tenfour04 Jun 09 '20 at 17:18

2 Answers2

2

Because generic types in Java / Kotlin are invariant by default. variance

interface Foo<out T: Bar>

If you can't make it covariant, then make the list items covariant

val collection = mutableListOf<Foo<out Bar>>().apply {
    this.add(FooEx1())
    this.add(FooEx2())
}

 //or  val collection = mutableListOf(FooEx1(), FooEx2())
IR42
  • 8,587
  • 2
  • 23
  • 34
  • Sure, but that forces my functions on the Foo interface to produce Bar, not consume it, which isn't what I want :-( – Thomas Cook Jun 09 '20 at 15:04
  • Granted, I didn't state that in the OP. I'll update it now – Thomas Cook Jun 09 '20 at 15:04
  • Plus, I don't think you need to update the mutable list to state `out`, just declaring it on the interface is enough. – Thomas Cook Jun 09 '20 at 15:10
  • 1
    @ThomasCook remove `out` from interface and add it to type in list – IR42 Jun 09 '20 at 15:13
  • @ThomasCook I think you are confused with covariance and contravariance, read this https://proandroiddev.com/understanding-generics-and-variance-in-kotlin-714c14564c47 Comparator is subclass of Comparator, because you can compare any animal by size or any property Animal exhibits **but Comparator is not necessarily a Comparator** because you may want to compare them by feathers which isn't even a property of Animal, so you cannot compare Insects with Comparator. – Animesh Sahu Jun 09 '20 at 15:15
  • But note that if you use `Foo` you'll find you can't actually call `example`... – Alexey Romanov Jun 10 '20 at 08:05
0

So it'll crash at run time with the cast?

Here is example code that would crash:

val foo: Foo<Bar> = collection[0]
foo.example(Bar2())

So if you could create collection without a cast as in your ExampleDoesntCompile, you'd get code without any casts which throws a ClassCastExcepion.

This also shows why the interface can't be declared with out:

val foo: Foo<Bar> = FooEx1() // out would make this legal
foo.example(Bar2())

It would make sense to declare your interface with in, but this would mean a Foo<Bar> is a Foo<Bar1> and a Foo<Bar2>, not vice versa, so still wouldn't let you put FooEx1/2 into a collection of Foo<Bar>s.

Alexey Romanov
  • 167,066
  • 35
  • 309
  • 487