Follow

Keep Up to Date with the Most Important News

By pressing the Subscribe button, you confirm that you have read and are agreeing to our Privacy Policy and Terms of Use
Contact

Kotlin context sensitive generics

There are lots of examples of Kotlin generics but this is one I’ve not come across.

I’ve simplified it down to this:

open class Fruit
class Apple : Fruit()
class Orange : Fruit()

abstract class Helper<T : Fruit> {
    open fun save(something: T) {}
}

class AppleHelper : Helper<Apple>() {
    override fun save(something: Apple) {}
}

class OrangeHelper : Helper<Orange>() {
    override fun save(something: Orange) {}
}

fun process(theFruit: Fruit) {
    val myHelper: Helper<out Fruit>
    when (theFruit) {
        is Apple -> {
            myHelper = AppleHelper()
            myHelper.save(theFruit)     // this does compile (NOT expected)
        }
        is Orange -> {
            myHelper = OrangeHelper()
            myHelper.save(theFruit)     // this does compile (NOT expected)
        }
        else -> throw RuntimeException()
    }
    myHelper.save(theFruit)   // this doesn't compile (as expected)
}

Using the out keyword for the myHelper variable makes it a producer of Fruit, however the 2 locations marked allow it to act as a consumer of theFruit.

MEDevel.com: Open-source for Healthcare and Education

Collecting and validating open-source software for healthcare, education, enterprise, development, medical imaging, medical records, and digital pathology.

Visit Medevel

The third location, as expected, rejects theFruit into the save method.

So clearly Kotlin is clever and can tell within the blocks that myHelper can be a safe consumer – but where is this behaviour documented?

>Solution :

This is because of Kotlin’s smart casts (documentation and specification).

The compiler does some dataflow analysis on your code and concluded that after the line myHelper = AppleHelper(), myHelper must be of type AppleHelper, theFruit must also be of type Apple because this is in the is Apple branch of the when. Therefore, myHelper.save(theFruit) is valid.

Whether or not generics is used is not relevant. Kotlin does this for everything. e.g.

val randomNumber = Random.nextInt()
val foo: Any
when (randomNumber) {
    1 -> {
        foo = "String" // here foo will be String
        println(foo.length) // can use properties specific to String
    }
    2 -> {
        foo = 10 // here foo will be Int
        println(foo * foo) // can multiply Ints
    }
    else -> {}
}
Add a comment

Leave a Reply

Keep Up to Date with the Most Important News

By pressing the Subscribe button, you confirm that you have read and are agreeing to our Privacy Policy and Terms of Use

Discover more from Dev solutions

Subscribe now to keep reading and get access to the full archive.

Continue reading