Обобщения
Generics или обобщения представляют технику, посредством которой методы и классы могут использовать объекты, типы которых на момент определения классов и функций неизвестны. Обобщения позволяют определять шаблоны, в которые можно подставлять различные типы.
Какие задачи решают обобщения? Допустим, у нас есть следующий класс
Обобщенные типы
Обобщенные типы (generic types) представляют типы, в которых типы объектов параметризированы. Что это значит? Рассмотрим следующий класс:
class Person(val id: T, val name: String)
Класс Person использует параметр T . Параметры указываются после имени класса в угловых скобках. Данный параметр будет представлять некоторый тип данных, который на момент определения класса неизвестен.
В первичном конструкторе определяется свойство id , которое представляет идентификатор. Оно представляет тип, который передается через параметр T. На момент определения класса Person мы не знаем, что это будет за тип.
Само название параметра произвольное (если оно не совпадает с ключевыми словами). Но нередко используется T как сокращение от слова type.
При использовании типа Person необходимо его типизировать определенным типом, то есть указать, какой тип будет передаваться через параметр T:
fun main() < val tom: Person= Person(367, "Tom") val bob: Person = Person("A65", "Bob") println("$ - $") println("$ - $") > class Person(val id: T, val name: String)
Для типизации объекта после названия типа в угловых скобках указывается конкретный тип:
В данном случае мы говорим, что параметр T фактически будет представлять тип Int. Поэтому в конструктор объекта Person для свойства id необходимо передать числовое значение Int:
Второй объект типизируется типом String, поэтому в конструкторе для свойства id передается строка:
val bob: Person = Person("A65", "Bob")
Если конструктор использует параметр T, то в принципе мы можем не указывать, каким типом типизируется объект — данный тип будет выводиться из типа параметра конструктора:
val tom = Person(367, "Tom") val bob = Person("A65", "Bob")
При этом параметры типа могут широко применяться внутри класса, не только при определении свойств, но и в функциях:
fun main() < val tom = Person("qwrtf2", "Tom") tom.checkId("qwrtf2") // The same tom.checkId("q34tt") // Different >class Person(val id: T, val name: String) < fun checkId(_id: T)< if(id == _id)< println("The same") >else < println("Different") >> >
Здесь класс Person определяет функцию checkId() , которая проверяет, равен ли id значению параметра _id. При этом параметр _id имеет тип T — то есть он будет представлять тот же тип, что и свойство id.
Стоит отметить, что generic-типы широко используются в Kotlin. Самый показательный пример, который представлен классом — Array . Параметр класса определяет, элементы какого типа массив будет хранить:
val people: Array = arrayOf("Tom", "Bob", "Sam") val numbers: Array = arrayOf(1, 2, 3, 4)
Применение нескольких параметров
Можно одновременно использовать несколько параметров:
fun main() < var word1: Word= Word("one", "один") var word2: Word = Word("two", 2) println("$ - $") // one - один println("$ - $") // two - 2 > class Word(val source: K, var target: V)
В данном случае класс Word применяет два параметра — K и V. При создании объекта Word эти параметры могут представлять один и тот же тип, а могут представлять и разные типы.
Обобщенные функции
Функции, как и классы, могут быть обобщенными.
fun main() < display("Hello Kotlin") display(1234) display(true) >fun display(obj: T)
Функция display() параметризирована параметром T. Параметр также указывается в угловых скобках после слова fun и перед названием функции. Функция принимает один параметр типа T и выводит его значение на консоль. И при использовании функции мы можем передавать в нее данные любых типов.
Другой более практический пример — определим функцию, которая будет возвращать наибольший массив:
fun main() < val arr1 = getBiggest(arrayOf(1,2,3,4), arrayOf(3, 4, 5, 6, 7, 7)) arr1.forEach < item ->print("$item ") > // 3 4 5 6 7 7 println() val arr2 = getBiggest(arrayOf("Tom", "Sam", "Bob"), arrayOf("Kate", "Alice")) arr2.forEach < item ->print("$item ") > // Tom Sam Bob > fun getBiggest(args1: Array, args2: Array): Array < if(args1.size >args2.size) return args1 else return args2 >
Здесь функция getBiggest() в качестве параметров принимает два массива. При этом мы точно не значем, объекты какого типа эти массивы будут содержать. Однако оба массива типизированы параметром T, что гарантирует, что оба массива будут хранить объекты одного и того же типа. Внутри функции сравниваем размер массивов с помощью их свойства size и возвращаем наибольший массив.
Generic Function
Generic functions are functions that accept a type parameter, which allows different call sites to pass different types, without sacrificing type safety. Yeah, that’s a mouthful, so let’s just take a look at an example.
Example
Definition
Here’s a Kotlin generic function that returns the middle-most item out of a List :
fun middleItem(list: List): T = list[list.size / 2]
- A type parameter of type T . This type parameter declaration goes before the name of the function, and it’s written here as .
- An argument that accepts a List of items that are of type T .
- A return type of T , which is the same as the type of the items in the List .
What exactly is the type of T ? Well, it depends on the call sites, so let’s look at how we’d call this function.
Usage
val middleInteger: Int = middleItem(listOf(1, 2, 3, 4, 5)) val middleString: String = middleItem(listOf("one", "two", "three"))
As this code demonstrates, our generic function can work with both List and List . (In fact, it can work with a List of any type!) When this function returns the result, the type is preserved. In other words, we didn’t have to cast the result back to an Int or String .
Comparison with Java
Generic functions in Kotlin are similar to Java, but the type parameter is declared in slightly different locations:
fun middleItem(list: List): T = // .
This shouldn’t trip you up much, since the real difference is that, in Kotlin, the return type has simply been moved to be after the function.
Alternatives
Abstract Arguments and/or Return Types
An alternative to our generic function above would be a function that accepts a more abstract type. For example, here we’ve got a function that accepts a List and returns Any .
fun middleItem(list: List): Any = list[list.size / 2]
As mentioned above, the trade-off is the casting that’s required at the call sites. Notice that we have to add as Int and as String here:
val middleInteger: Int = middleItem(listOf(1, 2, 3, 4, 5)) as Int val middleString: String = middleItem(listOf("one", "two", "three")) as String
It makes sense to use a generic function when you want to preserve the type. Otherwise, if you just need to invoke a function polymorphically, then accepting a particular abstract type is appropriate.
© 2017-2023 | Code snippets are licensed under Apache 2 unless otherwise noted. | Disclaimer | Privacy Policy | News Archives let expires = new Date(0).toGMTString(); document.cookie = «_jsuid=; expires=» + expires + «; path=/; domain=.typealias.com;» document.cookie = «cookieconsent_status=; expires Clicky» width=»1″ height=»1″ src=»//in.getclicky.com/101118828ns.gif»/>