Обобщения Kotlin (Generic) на простом примере

Обобщения в kotlin
kotlin generics

Привет. Сегодня будет короткий пост про обобщения Kotlin. Тема конечно изъезжена всеми вдоль и поперек, но это не умоляет ее ценности.

Итак, все знают, что списки могут содержать в себе элементы любого типа, и все это благодаря обобщениям, которые позволяют работать с типами еще неизвестными компилятору или Вам. Обобщенный тип в Kotlin — это класс, конструктор которого принимает аргументы
любого типа. Но, довольно теории, давайте посмотрим на практике что да как.

Объявление обобщения Kotlin

К примеру, мы пишем программу по учету преступников и у нас есть класс Gangster, в котором есть пара параметров — id и name. Так же есть метод — getInfo, который выводит информацию о нем в строку. Кроме того, есть класс — Cage (клетка), в который мы можем поместить в качестве параметра нашего преступник и указать уровень безопасности, необходимый для его содержания.

class Gangster(val id: Int, val name: String) {
    fun getInfo(): String {
        return "ID: $id, Name: $name"
    }
}

class Cage(var gangster: Gangster, val level: Int)

И все вроде хорошо. Мы можем создать преступника и камеру для него

val alik = Gangster(4253, "Alik Kutlov")
val cage = Cage(alik, 6)

И все вроде хорошо, но есть несколько проблем. Во-первых, в камеру мы можем поместить только определенный вид преступника — Gangster, но что если у нас много типов преступников — от мелких шалопаев до опасных коррупционеров? К тому же, пол у них возможен разный.

Конечно, мы можем написать еще класс камеры, например FemalCage, но это приведет лишь к дублированию кода, что не есть хорошо. Давайте посмотрим, как обобщения kotlin помогут решить нашу проблему

class Cage<T>(var gangster: T, val level: Int)

Обратите внимание, что параметр обобщенного типа часто обозначается единственной буквой Т (в скобочках вида < >), сокращенно от «type» (тип) слова «тип», хотя точно так же можно использовать любую букву или слово. Просто среди разработчиков принято обозначать T — как тип или класс, V — как переменную.

val vera = Gangster(5401, "Vera Farmova")
val vasya = Gangster(0954, "Vasiliy Svetlov")
val femCage = Cage(vera, 2)
val cage = Cage(vasya, 6)

Отлично, проблема решена. Можно так же переписать и класс для преступника, сделав общий и пару штук под каждый пол. Но теперь возникло еще одно затруднение, ведь можно например сделать так…

val newcage: Cage<String> = Cage(gangster = "This cage is a bullsh**t", level = 0)

Упс. Что-то явно пошло не так, хотя мы указали обобщение в Kotlin правильно. Решить эту проблему можно так:

class Cage<T : Gangster>(var gangster: T, val level: Int)

Теперь мы ограничили наш T только классами, унаследованными от Gangster. Кстати, больше информации про обобщения kotlin (generics) можно прочитать в официальной документации.

Функции с обобщениями

Но не только классы могут работать с обобщёнными типами данных. К примеру, мы хотим написать функцию расширения для всех списков и массивов, которая выводит их значения в одну строку. Да, такой метод уже есть, но мы накидаем свой ради примера.

fun <T>Iterable<T>.stringify(): String {
    return this.joinToString(" ")
}

val cars = listOf("Ford", "Mazda", "Kia")
println(cars.stringify()) // "Ford Mazda Kia"

Теперь если мы вызовем метод stringify() то получим строку из элементов массива, разделенную пробелом. И это будет работать, даже если мы укажем Any

val arr: List<Any> = listOf("Ford", "Mazda", "Kia", 765, true)
println(arr.stringify()) // "Ford Mazda Kia 765 true"

Давайте разберем эту строку — fun <T>Iterable<T>. Первый параметр <T> говорит от том, что функция stringify принимает обобщённый тип Iterable<T>, то есть исчисляемый объект — к примеру массивы, списки, которые могут состоять из любых типов (Int, String, Boolean и т.д). А так можно написать свойство расширения

val <T>List<T>.getFirst: T
    get() = this[0]

var pc = listOf("Dell", "Mac", "Lenovo")
var fib = listOf(1, 1, 2, 3, 5, 8, 13)
println("PC: ${pc.getFirst}, Fib: ${fib.getFirst}") // PC: Dell, Fib: 1

…которое вернет первый элемент из списка. Кстати в следующей статье мы подробно разберем функции и свойства расширения в Котлин.

Оставить ответ