Обобщения Kotlin
Обобщения, то есть "параметризованные типы", параметризуют типы и могут применяться к классам, интерфейсам и методам.
Как и Java, Kotlin также предоставляет обобщения, обеспечивающие типобезопасность и избавляющие от хлопот с приведением типов.
Объявление обобщённого класса:
class Box<T>(t: T) {
var value = t
}
При создании экземпляра класса нам нужно указать параметр типа:
val box: Box<Int> = Box<Int>(1) // 或者 val box = Box(1) // 编译器会进行类型推断,1 类型 Int,所以编译器知道我们说的是 Box<Int>。
В следующем примере в обобщённый класс Box передаются целочисленные данные и строка:
class Box<T>(t : T) {
var value = t
}
fun main(args: Array<String>) {
var boxInt = Box<Int>(10)
var boxString = Box<String>("Example")
println(boxInt.value)
println(boxString.value)
}
Результат вывода:
10 Example
При определении переменной обобщённого типа можно полностью записать параметр типа или опустить его, если компилятор может автоматически вывести параметр типа.
Объявление обобщённых функций в Kotlin такое же, как в Java; параметр типа следует размещать перед именем функции:
fun <T> boxIn(value: T) = Box(value) // 以下都是合法语句 val box4 = boxIn<Int>(1) val box5 = boxIn(1) // 编译器会进行类型推断
При вызове обобщённой функции, если параметр типа можно вывести, обобщённый параметр можно опустить.
В следующем примере создаётся обобщённая функция doPrintln, которая соответствующим образом обрабатывает переданные различные типы:
fun main(args: Array<String>) {
val age = 23
val name = "example"
val bool = true
doPrintln(age) // 整型
doPrintln(name) // 字符串
doPrintln(bool) // 布尔型
}
fun <T> doPrintln(content: T) {
when (content) {
is Int -> println("整型数字为 $content")
is String -> println("字符串转换为大写:${content.toUpperCase()}")
else -> println("T 不是整型,也不是字符串")
}
}
Результат вывода:
整型数字为 23 字符串转换为大写:EXAMPLE T 不是整型,也不是字符串
Ограничения обобщений
Мы можем использовать ограничения обобщений, чтобы задать допустимые типы для данного параметра.
В Kotlin символ : используется для ограничения верхней границы обобщённого типа.
Наиболее распространённым ограничением является верхняя граница:
fun <T : Comparable<T>> sort(list: List<T>) {
// ……
}
Comparable
sort(listOf(1, 2, 3)) // OK。Int 是 Comparable<Int> 的子类型 sort(listOf(HashMap<Int, String>())) // 错误:HashMap<Int, String> 不是 Comparable<HashMap<Int, String>> 的子类型
Верхняя граница по умолчанию — Any?.
Для нескольких ограничений верхней границы можно использовать конструкцию where:
fun <T> copyWhenGreater(list: List<T>, threshold: T): List<String>
where T : CharSequence,
T : Comparable<T> {
return list.filter { it > threshold }.map { it.toString() }
}
Вариантность
В Kotlin нет типов с подстановочными знаками; есть две другие возможности: вариантность на уровне объявления и проекции типов.
Вариантность на уровне объявления
Вариантность на уровне объявления использует ковариантные модификаторы-аннотации: in и out. Потребитель — in, производитель — out.
Использование out делает параметр типа ковариантным. Ковариантный параметр типа может использоваться только на выходе, а также как тип возвращаемого значения, но не как тип входного параметра:
// 定义一个支持协变的类
class Example<out A>(val a: A) {
fun foo(): A {
return a
}
}
fun main(args: Array<String>) {
var strCo: Example<String> = Example("a")
var anyCo: Example<Any> = Example<Any>("b")
anyCo = strCo
println(anyCo.foo()) // 输出 a
}
Использование in делает параметр типа контравариантным. Контравариантный параметр типа может использоваться только на входе, а также как тип входного параметра, но не как тип возвращаемого значения:
// 定义一个支持逆变的类
class Example<in A>(a: A) {
fun foo(a: A) {
}
}
fun main(args: Array<String>) {
var strDCo = Example("a")
var anyDCo = Example<Any>("b")
strDCo = anyDCo
}
Звёздная проекция
Иногда может потребоваться указать, что вы не знаете никакой информации о параметре типа, но всё ещё хотите безопасно его использовать. Так называемое «безопасное использование» здесь означает определение проекции типа для обобщённого типа, требующей, чтобы все конкретные экземпляры этого обобщённого типа были подтипами этой проекции.
Для решения этой проблемы Kotlin предоставляет синтаксис, называемый звёздной проекцией:
- Если тип определён как Foo<out T>, где T — ковариантный параметр типа с верхней границей TUpper, то Foo<> эквивалентно Foo<out TUpper>. Это означает, что когда T неизвестен, вы можете безопасно читать значение типа TUpper из Foo<>.
- Если тип определён как Foo<in T>, где T — контравариантный параметр типа, то Foo<> эквивалентно Foo<in Nothing>. Это означает, что когда T неизвестен, вы не можете безопасно записать в Foo<> ничего.
- Если тип определён как Foo<T>, где T — ковариантный параметр типа с верхней границей TUpper, то для чтения значений Foo<*> эквивалентно Foo<out TUpper>; для записи значений — эквивалентно Foo<in Nothing>.
Если у обобщённого типа несколько параметров типа, каждый параметр типа можно проецировать независимо. Например, если тип определён как interface Function<in T, out U>, могут появляться следующие звёздные проекции:
- Function<*, String>, что соответствует Function<in Nothing, String>;
- Function<Int, *>, что соответствует Function<Int, out Any?>;
- Function<, >, что соответствует Function<in Nothing, out Any?>.
Примечание: звёздная проекция очень похожа на raw-типы Java, но её можно безопасно использовать.
Другие расширения