Generic In and Out
If we take a look at our Aquarium class of the previous example, we can see that the generic type is only ever returned by the property water supply.We didn't define any functions that take a value of type t as a parameter. Kotlin let's us define out types for exactly this case.
Out types are type parameters that only ever occur in return values of functions, or on Val properties.
If we try to pass an out type as a parameter to a function, Kotlin will give us a compiler error.
Once we make a generic type and out type, Kotlin can infer extra information about where our types are safe to use.
For example, let's declare a function, addItemTo that expects an aquarium of water supply.We can call addItemTo on an aquarium of tap water. Kotlin can ensure that addItemTo won't do anything unsafe with a generic, because it's declared as an out type.
Main.kt function
import generics.Aquarium
// File: Main.kt
// Programmer: Engineer Nolverto Urias Obeso
// Creation Date: 06/24/2023
// Email: nolvertou@gmail.com
// Description: Using a generic class
fun main() {
genericExample()
}
// Kotlin can ensure that addItemTo won't do anything unsafe with a generic,
// because it's declared as an out type.
fun addItemTo(aquarium: Aquarium<WaterSupply>) = println("item added")
fun genericExample(){
// Example 1: Using generic class
val aquarium = Aquarium<TapWater>(TapWater())
println("The waterSupply needs to be processed: "+aquarium.waterSupply.needsProcessed)
addItemTo(aquarium) // We can call addItemTo on an aquarium of tap water. Kotlin can ensure that addItemTo
// won't do anything unsafe with a generic, because it's declared as an out type.
// addItemTo(aquarium) // If we made the type t not be an out type, we see that Kotlin gives us
// a compiler error when calling addItemTo. Kotlin does this because it can't ensure we're using it safely.
}
Aquarium Generic Class it's declared as an out type.
package generics
import WaterSupply
// NOTE: Kotlin can infer extra information about where our types are safe to use
class Aquarium<out T: WaterSupply>(val waterSupply: T){
fun addWater(){
// This acts as an assertion, and it will throw an illegal exception if its argument is false
check(!waterSupply.needsProcessed){"Water Supply needs processed!"}
println("adding water from $waterSupply")
}
}
Output:
The waterSupply needs to be processed: true
item added
If we made the type t not be an out type, we see that Kotlin gives us a compiler error when calling addItemTo. Kotlin does this because it can't ensure we're using it safely.
23:15
Kotlin: Type mismatch: inferred type is Aquarium<TapWater> but Aquarium<WaterSupply> was expected
In types are from the same school as out types. In types can be used anytime the generic is only used as an argument to functions.
More specifically,
in types can only be passed into an object.
Out types can only be passed out of an object or returned.
There's one special time you can pass an out type. Constructors can take out types as arguments,but functions never can. The compiler will enforce this.
If we try to return an in type, we'll get an error.
As an example, honestly a bit contrived, let's say we wanted to make an interface cleaner that let's us clean different water supplies. We'll give cleaner a generic t that's a water supply, and since we're only ever using t as an argument to clean, we can make it an in type.
import generics.Aquarium
import generics.TapWaterCleaner
// File: Main.kt
// Programmer: Engineer Nolverto Urias Obeso
// Creation Date: 06/24/2023
// Email: nolvertou@gmail.com
// Description: Using a generic class
fun main() {
genericExample()
}
// Kotlin can ensure that addItemTo won't do anything unsafe with a generic,
// because it's declared as an out type.
fun addItemTo(aquarium: Aquarium<WaterSupply>) = println("item added")
fun genericExample(){
val cleaner = TapWaterCleaner()
val aquarium = Aquarium<TapWater>(TapWater())
println("The waterSupply needs to be processed: "+aquarium.waterSupply.needsProcessed)
addItemTo(aquarium) // We can call addItemTo on an aquarium of tap water. Kotlin can ensure that addItemTo
// won't do anything unsafe with a generic, because it's declared as an out type.
// addItemTo(aquarium) // If we made the type t not be an out type, we see that Kotlin gives us
// a compiler error when calling addItemTo. Kotlin does this because it can't ensure we're using it safely.
aquarium.addWater(cleaner)
}
Aquarium Generic Class
package generics
import WaterSupply
// NOTE: Kotlin can infer extra information about where our types are safe to use
class Aquarium<out T: WaterSupply>(val waterSupply: T){
fun addWater(cleaner: Cleaner<T>){
// This acts as an assertion, and it will throw an illegal exception if its argument is false
if (waterSupply.needsProcessed){
cleaner.clean(waterSupply)
}
println("adding water from $waterSupply")
}
}
TapWater Child Class
class TapWater: WaterSupply(true) {
fun addChemicalCleaners(){
needsProcessed = false
}
}
WaterSupply Parent Class
open class WaterSupply(var needsProcessed: Boolean)
TapWaterCleaner Class
package generics
import TapWater
class TapWaterCleaner: Cleaner<TapWater> {
override fun clean(waterSupply: TapWater) {
println("Cleaning the water supply")
waterSupply.addChemicalCleaners()
}
}
Interface Class
package generics
import WaterSupply
interface Cleaner<in T: WaterSupply> {
// Since we're only ever using T as an argument to clean we can make it "in" type
fun clean(waterSupply: T)
}
To use the cleaner interface,we'll make a class TapWaterCleaner that implements Cleaner for cleaning tap water by adding chemicals.
Now, in our Aquarium class,we can update add water to take a Cleaner of type T, and we can now clean the water before we add it.Kotlin will figure out what this means, and make sure your generics usage is safe. Now, when a WaterSupply needs processed, we can use Cleaner to clean it.
Finally, we can use our Aquarium like this, make a new tap WaterCleaner, then make a new Aquarium of TapWater. When we call addWater, we'll pass the cleaner to addWater.
out and in are really easy to remember.
Out types can be used as return values,and
in types can be used as parameters.
The IDE will suggest you add out or in to your generic types when it's correct to do so.So, if you forget, look for the warning to add out and in. If you want to dig into the sort of problems that they solve, the documentation covers them in depth.
That was a lot of explanations. Fortunately, IntelliJ gives you hints as to whether something should be an in or out type in your current code.
Look at the code from the previous practice and consider whether it can be an in type or an out type. Notice that the parameter is underlined gray, and if you hover over T, IntelliJ will suggest to make it an “out” type.
![]() |
Figure 1. Out type Sugesstion |
![]() |
Figure 2. Intype Sugesstion |
Comments
Post a Comment