out
Is a Direction
out
indicates that our dominant use of the generic type is to return values of that type. And, as such, we believe that it is safe for us to support sub-types of that type.
Remember that in Kotlin (and many strongly-typed object-oriented programming languages) a variable or property type need only be compatible with its value’s actual type. Objects have type — a variable or property’s type says “we are using this object as if it its type were X”, regardless of the actual type of that object.
So, suppose that MutableList
allowed subtypes and this statement succeeded:
val animals: MutableList<Animal> = mutableListOf<Frog>(Frog(), Frog())
animals
says “I hold a list of Animal
objects”, and it allows you to assign any Animal
to any index. So, from a compilation standpoint, this would work:
animals[0] = Axolotl()
After all, Axolotl
is an Animal
.
However, while animals
says “I hold a list of Animal
objects”, the actual object that animals
points to says “I hold a list of Frog
objects”. We cannot put an Axolotl
in a List<Frog>
, and so animals[0] = Axolotl()
would cause one of two problems:
- It would fail immediately, on the grounds that you cannot put an
Axolotl
in aList<Frog>
- It would fail when we later try retrieving that element, because Kotlin would try treating an
Axolotl
as if it were aFrog
, and that would result in aClassCastException
or similar problem
So, MutableList
is invariant in its generic type. A MutableList<Frog>
needs to be based on a list of Frog
objects, and if we have a MutableList<Animal>
variable or property, the actual underlying list needs to support any type of Animal
, not just one sub-type.
But List
does not support mutation. If you review the functions on List
, either:
- They return the generic type, or
- They accept the generic type but only for comparison purposes (e.g.,
contains()
to determine if the list contains a certain element)
In other words, for the operations that List
performs, it does not matter if the underlying list is of a sub-type or not. All the List
operations still work.
That is why List
can use out
to say that it is covariant in the generic type. A List<Animal>
variable or property can point to a List<Frog>
without issue, because at no point will we be forced to try to put an Axolotl
into that List
.
Prev Table of Contents Next
This book is licensed under the Creative Commons Attribution-ShareAlike 4.0 International license.