Шаг 205.
Основы Kotlin.
Обобщения. Конструкции in и out

    На этом шаге мы рассмотрим необходимость использования этих конструкций.

    Для дальнейших изменений ваших параметров обобщенного типа Kotlin предоставляет ключевые слова in и out. Чтобы посмотреть на их работу, создайте простой обобщенный класс Barrel в новом файле с именем Variance.kt.

class Barrel<T>(var item: T)


Рис.1. Объявление Barrel (Variance.kt)

    Для экспериментов с Barrel добавим функцию main(). В main() объявите Barrel для хранения Fedora и еще один Barrel для хранения Loot.

class Barrel<T>(var item: T)

fun main(args: Array<String>) {
    var fedoraBarrel: Barrel<Fedora> =
            Barrel(Fedora("a generic-looking fedora", 15))
    var lootBarrel: Barrel<Loot> = Barrel(Coin(15))
}


Рис.2. Объявление двух Barrel в main() (Variance.kt)

    Хотя Barrel<Loot> может хранить награды любого типа, конкретный объявленный экземпляр в примере хранит Coin (подкласс Loot).

    Теперь присвойте переменной lootBarrel экземпляр fedoraBarrel.

class Barrel<T>(var item: T)

fun main(args: Array<String>) {
    var fedoraBarrel: Barrel<Fedora> =
            Barrel(Fedora("a generic-looking fedora", 15))
    var lootBarrel: Barrel<Loot> = Barrel(Coin(15))

    lootBarrel = fedoraBarrel
}


Рис.3. Попытка присвоить значение lootBarrel (Variance.kt)

    Удивительно, но эта попытка будет отвергнута компилятором (рисунок 3, несоответствие типа).

    Казалось бы, такое присваивание должно быть допустимо. Fedora все-таки является наследником Loot, и переменной типа Loot вполне можно присвоить экземпляр Fedora:

  var loot: Loot = Fedora("a generic-looking fedora", 15) // Нет ошибки

    Чтобы понять, в чем проблема, давайте поразмышляем, что получится, если такое присваивание выполнится успешно.

    Если компилятор разрешит присвоить экземпляр fedoraBarrel переменной lootBarrel, тогда lootBarrel будет указывать на fedoraBarrel и появится возможность взаимодействовать с наградой в fedoraBarrel так, будто это Loot, а не Fedora (потому что тип lootBarrel - это Barrel<Loot>).

    Например, монеты - это один из подтипов Loot, поэтому свойству item экземпляра lootBarrel (который указывает на fedoraBarrel) можно было бы присвоить монеты. Сделайте так в Variance.kt.

class Barrel<T>(var item: T)

fun main(args: Array<String>) {
    var fedoraBarrel: Barrel<Fedora> =
            Barrel(Fedora("a generic-looking fedora", 15))
    var lootBarrel: Barrel<Loot> = Barrel(Coin(15))

    lootBarrel = fedoraBarrel
    lootBarrel.item = Coin(15)
}


Рис.4. Присваивание монет свойству lootBarrel.item (Variance.kt)

    Теперь предположим, что вы обращаетесь к fedoraBarrel.item, ожидая получить шляпу.

class Barrel<T>(var item: T)

fun main(args: Array<String>) {
    var fedoraBarrel: Barrel<Fedora> =
            Barrel(Fedora("a generic-looking fedora", 15))
    var lootBarrel: Barrel<Loot> = Barrel(Coin(15))

    lootBarrel = fedoraBarrel
    lootBarrel.item = Coin(15)
    val myFedora: Fedora = lootBarrel.item
}


Рис.5. Обращение к fedoraBarrel.item (Variance.kt)

    Компилятор столкнется с несоотвествием типа - fedoraBarrel.item не Fedora, а Coin, - и вы получите исключение ClassCastException. Теперь у вас возникла проблема, и именно по этой причине компилятор изначально запретил присваивание.

    Для решения этой проблемы и были придуманы ключевые слова in и out.

    В объявлении класса Barrel добавьте ключевое слово out и измените item c var на val.

class Barrel<out T>(val item: T)


Рис.6. Добавление out (Variance.kt)

    Далее удалите строку кода, в которой присваивали Coin свойству item (что теперь запрещено, так как item уже val), и присвойте свойству lootBarrel.item переменную myFedora вместо fedoraBarrel.item.

class Barrel<out T>(val item: T)

fun main(args: Array<String>) {
    var fedoraBarrel: Barrel<Fedora> =
            Barrel(Fedora("a generic-looking fedora", 15))
    var lootBarrel: Barrel<Loot> = Barrel(Coin(15))

    lootBarrel = fedoraBarrel
    val myFedora: Fedora = lootBarrel.item
}


Рис.7. Изменение присваивания (Variance.kt)

    На следующем шаге мы закончим изучение этого вопроса.




Предыдущий шаг Содержание Следующий шаг