I want to understand in
and out
in Kotlin. As I found theory is, consumer in
takes and producer out
returns.
But how does two methods below differentiate when in
and out
are taken as method arguments where we can access list
without any issue?
private fun exampleMethod1(list: ArrayList<out String>) {}
private fun exampleMethod2(list: ArrayList<in String>) {}
"Out" keyword is extensively used in Kotlin generics. Its signature looks like this − List<out T> When a type parameter T of a class C is declared out, then C can safely be a super type of C<Derived>. That means, a Number type List can contain double, integer type list.
In the event one generic class uses the generic type as input and output to it's function, then no in or out is used. It is invariant.
In Kotlin, You can pass a variable number of arguments to a function by declaring the function with a vararg parameter. a vararg parameter of type T is internally represented as an array of type T ( Array<T> ) inside the function body.
In certain computer programming languages, the Elvis operator ?: is a binary operator that returns its first operand if that operand is true , and otherwise evaluates and returns its second operand.
Let me demonstrate what in
/out
do with the help of an example. Consider the following:
private fun foo(list: ArrayList<Number>) {}
private fun bar(list: ArrayList<Number>) {}
Now we try to pass an ArrayList
to each function, each with a different generic type parameter:
// Error: Type Mismatch. Required `ArrayList<Number>` Found `ArrayList<Int>`
foo(arrayListOf<Int>())
// Error: Type Mismatch. Required `ArrayList<Number>` Found `ArrayList<Any>`
bar(arrayListOf<Any>())
But we get errors! How do we solve that? We have to tell the compiler somehow that, for foo
the list can also contain elements of a subtype of Number
(e.g. Int
) and for bar
we have to tell the compiler that the list can also contain elements of a basetype of Number
(e.g. Any
).
private fun foo(list: ArrayList<out Number>) {}
private fun bar(list: ArrayList<in Number>) {}
And now it works!
Further reading
When the modifier in
or out
is used in the function parameter, it's called type projection.
Projections | Produces | Consumes | Function Behaviour |
---|---|---|---|
ArrayList<out Orange> |
Orange |
Nothing |
Accepts subtypes of ArrayList<Orange>
|
ArrayList<in Orange> |
Any? |
Orange |
Accepts supertypes of ArrayList<Orange>
|
ArrayList<Orange> |
Orange |
Orange |
Doesn't accept any subtypes or supertypes |
ArrayList
in Kotlin is a producer as well as a consumer. This is because it's an invariant generic class defined as ArrayList<T>
,not as ArrayList<out T>
(producer) or ArrayList<in T>
(consumer). This means the class can have functions that accept T
as a function parameter (consume) or return T
(produce).
But what if you want to use this already existing class safely just as a consumer(in T
) or just as a producer(out T
)? Without worrying about the accidental use of the other unwanted functionality?
In that case, we project the types by using variance modifiers in
and out
at the use-site. Use-site simply means wherever we use the ArrayList<T>
class.
out
produces T
and the function accepts subtypesWhen you are using the ArrayList
as a producer(out
), the function can accept the subtypes of ArrayList<Orange>
, that is, ArrayList<MandarinOrange>
, ArrayList<BloodOrange>
, since MandarinOrange
and BloodOrange
are subtypes of Orange
. Because the subtyping is preserved:
fun useAsProducer(producer: ArrayList<out Orange>) {
// Producer produces Orange and its subtypes
val orange = producer.get(1) // OK
// Can use functions and properties of Orange
orange.getVitaminC() // OK
// Consumer functions not allowed
producer.add(BloodOrange()) // Error
}
The producer
produces Orange
and its subtypes. Here producer.get(1)
can return MandarinOrange
, BloodOrange
etc. but we don't care as long as we get an Orange
. Because we are only interested in using the properties and functions of Orange
at use-site.
Compiler doesn't allow calling the add()
function (consumer) because we don't know which type of Orange
it contains. You don't want to accidentally add BloodOrange
, if this is an ArrayList<MandarinOrange>
.
in
consumes T
and the function accepts supertypesWhen you are using ArrayList
as a consumer(in
), the function can accept the supertypes of ArrayList<Orange>
, that is, ArrayList<Fruit>
because now the subtyping is reversed. This means ArrayList<Fruit>
is a subtype of ArrayList<Orange>
when Orange
is a subtype of Fruit
:
fun useAsConsumer(consumer: ArrayList<in Orange>) {
// Produces Any?, no guarantee of Orange because this could
// be an ArrayList<Fruit> with apples in it
val anyNullable = consumer.get(1) // Not useful
// Not safe to call functions of Orange on the produced items.
anyNullable.getVitaminC() // Error
// Consumer consumes Orange and its subtypes
consumer.add(MandarinOrange()) // OK
}
The consumer
consumes Orange
and its subtypes. It doesn't matter whether it's a MandarinOrange
or a BloodOrange
as long as it's an Orange
. Because the consumer
is only interested in the properties and functions of Orange
at its declaration-site.
The compiler does allow the call to get()
function (producer) but it produces Any?
which is not useful for us. The compiler flags an error when you use that Any?
as an Orange
at use-site.
T
, the function doesn't accept subtypes or supertypesWhen you are using ArrayList
as a producer and consumer (without in
or out
), the function can accept only the exact type ArrayList<Orange>
, no other subtypes like ArrayList<MandarinOrange>
or supertypes like ArrayList<Fruit>
. Because the subtyping is not allowed for invariants:
fun useAsProducerConsumer(producerConsumer: ArrayList<Orange>) {
// Produces Orange and its subtypes
val orange = producerConsumer.get(1) // OK
// Orange is guaranteed
orange.getVitaminC() // OK
// Consumes Orange and its subtypes
producerConsumer.add(Orange()) // OK
}
The invariant produces and consumes Orange
and its subtypes.
That's it! Type projection is all about telling the compiler how you are using that class in that particular function, so it can help you by flagging an error if you call unintended functions accidentally. Hope that helps.
For in generic, we could assign a class of super-type to class of sub-type. But for out generic, we could assign a class of sub-type to class of super-type
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With