Освой Kotlin играючи

Сайт Александра Климова

Шкодим

/* Моя кошка замечательно разбирается в программировании. Стоит мне объяснить проблему ей - и все становится ясно. */
John Robbins, Debugging Applications, Microsoft Press, 2000

Обобщения

Для объяснения обобщений я воспользовался примерами из книги "Head First Kotlin", потому что там были котики!

При работе с коллекциями нам приходилось иметь дело с обобщениями.


val cats = mutableListOf<Cat>()

Изучим их поподробнее, так как это важная тема.

Тип элемента определяется в угловых скобках <> — это означает, что он использует обобщения. Обобщения позволяют писать код, безопасный по отношению к типам. Это не позволяет поместить объект Dog (сраный пёсик) в список с элементами Cat. Компилятор знает, что в MutableList<Cat> можно добавлять только объекты Cat, а следовательно, на стадии компиляции будет выявляться большее количество проблем.

Если посмотреть в документации на определение MutableList, то увидим следующее.


interface MutableList<E> : List<E>, MutableCollection<E> {
	fun add(index: Int, element: E): Unit
	...
}

MutableList использует «E» (от слова Element) как условное обозначение типа элемента, который должен храниться и возвращаться коллекцией. Когда вы видите «E» в документации, мысленно подставьте на это место тот тип, который должен храниться в коллекции. Например, MutableList<String> означает, что «E» превращается в «String» в любой функции или объявлении переменной, в которых используется «E». А MutableList<Cat> означает, что все вхождения «E» превращаются в «Cat».

При таком подходе срабатывает защита от дурака. Если вы создадите MutableList с типом Cat, функция add() позволит добавлять только объекты Cat, но не пропустит объекты String и другие чужие объекты.

Можно создать функцию с обобщённым параметром:


fun meow(cats: MutableList<Cat>) {
    //...
}

Можно создать функцию, которая возвращает обобщённый тип. Например, следующий код возвращает список MutableList с элементами Cat:


fun getCats(breed: String): MutableList<Cat> {
    //Получение объектов Cat
}

Теперь подробнее о том, как пользоваться обобщениями. Создадим абстрактный класс Pet и три производных класса от него: Кот, Пёс и Рыбка.


abstract class Pet(var name: String)

class Cat(name: String) : Pet(name)
class Dog(name: String) : Pet(name)
class Fish(name: String) : Pet(name)

Также нам понадобится класс "Выставка", которая используется для проведения выставок разных типов животных с выдачей оценок и определения победителя. С его помощью мы будем управлять оценками участников для определения победителя. Ни разу не видел, чтобы одновременно проходила выставка котов и рыбок, поэтому логично ограничить класс только определённым типом животного. В выставке кошек участвуют только кошки, а в конкурсе рыбок — только рыбки. При определении класса Contest будут использоваться обобщения, чтобы ограничиться животными определённого типа.


class Contest<T> {
    val scores: MutableMap<T, Int> = mutableMapOf()

    fun addScore(t: T, score: Int = 0) {
        if (score >= 0) scores.put(t, score)
    }

    fun getWinners(): MutableSet<T> {
        val highScore = scores.values.max()
        val winners: MutableSet<T> = mutableSetOf()
        for ((t, score) in scores) {
            if (score == highScore) winners.add(t)
        }
        return winners
    }
}

Чтобы указать, что класс использует обобщённый тип, укажите имя типа в угловых скобках после имени класса. В данном случае обобщённый тип обозначается T. Считайте, что T заменяет реальный тип, который используется каждым отдельным объектом Contest. Вы можете использовать любую букву, но принято использовать в подобных случаях именно T. Кстати, если создаётся класс коллекции или интерфейс, то принято использовать E (сокращение от «Element»), а для ключей и значений массивов — K или V (сокращения от «Key» и «Value»).

Но нам не нужен слишком обобщённый тип, поэтому установим ограничение, указав нужный тип. Мы хотим, чтобы тип T был разновидностью Pet:


class Contest<T : Pet> {
	...
}

Это означает, что мы можем создавать объекты Contest для хранения Cat, Fish или Pet, но не для объектов Ball или Flower.

Реклама