Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

What does in/out actually do in Kotlin when passed as arguments?

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>) {}
like image 341
channae Avatar asked Apr 14 '19 17:04

channae


People also ask

What is use of out in Kotlin?

"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.

What is in and out in Kotlin generics?

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.

How do you pass arguments in Kotlin?

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.

What does ?: Mean in Kotlin?

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.


Video Answer


3 Answers

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

like image 96
Willi Mentzel Avatar answered Oct 03 '22 16:10

Willi Mentzel


Type Projection

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 subtypes

When 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 supertypes

When 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.


Invariant produces and consumes T, the function doesn't accept subtypes or supertypes

When 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.

like image 45
Yogesh Umesh Vaity Avatar answered Oct 03 '22 15:10

Yogesh Umesh Vaity


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

like image 23
Ahmad Mousavi Avatar answered Oct 03 '22 15:10

Ahmad Mousavi