First lets clearfy what do we get by prefixing a type parameter
with out
keyword
. consider the following class
:
class MyList<out T: Number>{
private val list: MutableList<T> = mutableListOf()
operator fun get(index: Int) : T = list[index]
}
out
keyword here makes MyList
covariant in T
, which essentially means you can do the following :
// note that type on left side of = is different than the one on right
val numberList: MyList<Number> = MyList<Int>()
if you remove the out keyword and try to compile again, you will get type mismatch error.
by prefixing the type
parameter
with out
, you are basically declaring the type
to be a producer of T
's, in the example above MyList
is a producer of Numbers.
which means no matter if you instantiate
T
as Int
or Double
or some other subtype of Number
, you will always be able to get a Number from MyList
(Because every subtype of Number
is a Number
). which also allows you to to do the following:
fun process(list: MyList<Number>) { // do something with every number }
fun main(){
val ints = MyList<Int>()
val doubles = MyList<Double>()
process(ints) // Int is a Number, go ahead and process them as Numbers
process(doubles) // Double is also a Number, no prob here
}
// if you remove out, you can only pass MyList<Number> to process
Now lets answer With out
keyword why T
should only be in return position? and what can happen without this constraint?, that is if MyList
had a function taking T
as parameter.
fun add(value: T) { list.add(T) } // MyList has this function
fun main() {
val numbers = getMyList() // numbers can be MyList<Int>, MyList<Double> or something else
numbers.add(someInt) // cant store Int, what if its MyList<Double> ( Int != Double)
numbers.add(someDouble) // cant do this, what if its MyList<Int>
}
// We dont know what type of MyList we going to get
fun getMyList(): MyList<Number>(){
return if(feelingGood) { MyList<Int> () }
else if(feelingOk> { MyList<Double> () }
else { MyList<SomeOtherSubType>() }
}
that is why constraint is required, its basically there to guarantee type safety.
as for abstract class E<out T> (t:T) { val x = t }
being compiled, Kotlin In Action has following to say
Note that constructor parameters are in neither the in nor the out
position. Even if a type parameter is declared as out, you can still
use it in a constructor parameter. The variance protects the class
instance from misuse if you’re working with it as an instance of a
more generic type: you just can’t call the potentially dangerous
methods. The constructor isn’t a method that can be called later
(after an instance creation), and therefore it can’t be potentially
dangerous.