Kotlin generics
Generics, namely "parameterized types", parameterize types and can be used in classes, interfaces, and methods.
Like Java, Kotlin also provides generics, which guarantees type safety and eliminates the trouble of forced conversion.
Declare a generic class:
class Box<T>(t: T) {
var value = t
}
When creating an instance of the class, we need to specify the type parameter
val box: Box<Int> = Box<Int>(1)
// 或者
val box = Box(1) // 编译器会进行类型推断,1 类型 Int,所以编译器知道我们说的是 Box<Int>。
The following example passes integer data and strings to the generic class Box
class Box<T>(t : T) {
var value = t
}
fun main(args: Array<String>) {
var boxInt = Box<Int>(10)
var boxString = Box<String>("Runoob")
println(boxInt.value)
println(boxString.value)
}
The output result is
10
Runoob
To define a generic type variable, you can completely specify the type parameters. If the compiler can automatically infer the type parameters, you can also omit the type parameters.
The declaration of Kotlin generic functions is the same as that of Java, and the type parameters should be placed in front of the function name
fun <T> boxIn(value: T) = Box(value)
// 以下都是合法语句
val box4 = boxIn<Int>(1)
val box5 = boxIn(1) // 编译器会进行类型推断
When calling a generic function, if the type parameter can be inferred, the generic parameter can be omitted.
The following example creates a generic function doPrintln, and the function does the corresponding processing according to the different types passed in
fun main(args: Array<String>) {
val age = 23
val name = "runoob"
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 不是整型,也不是字符串")
}
}
The output result is
整型数字为 23
字符串转换为大写:RUNOOB
T 不是整型,也不是字符串
Generic constraints
We can use generic constraints to set the allowed types for a given parameter.
Used in Kotlin: Constrain the upper limit of generic types.
The most common constraint is the upper bound
fun <T : Comparable<T>> sort(list: List<T>) {
// ……
}
Subtypes of Comparable can replace T. E.g
sort(listOf(1, 2, 3)) // OK。Int 是 Comparable<Int> 的子类型
sort(listOf(HashMap<Int, String>())) // 错误:HashMap<Int, String> 不是 Comparable<HashMap<Int, String>> 的子类型
The default upper bound is Any?
For multiple upper bound constraints, you can use the where clause
fun <T> copyWhenGreater(list: List<T>, threshold: T): List<String>
where T : CharSequence,
T : Comparable<T> {
return list.filter { it > threshold }.map { it.toString() }
}
Type change
There is no wildcard type in Kotlin, it has two other things: declaration-site variance and type projections
Declaration
The type mutation at the declaration uses covariant annotation modifiers: in, out, consumer in, producer out.
Use out to make a type parameter covariant. Covariant type parameters can only be used as output. They can be used as return value types but cannot be used as input parameters.
// 定义一个支持协变的类
class Runoob<out A>(val a: A) {
fun foo(): A {
return a
}
}
fun main(args: Array<String>) {
var strCo: Runoob<String> = Runoob("a")
var anyCo: Runoob<Any> = Runoob<Any>("b")
anyCo = strCo
println(anyCo.foo()) // 输出 a
}
in makes a type parameter contravariant, contravariant type parameters can only be used as input, can be used as the type of the input parameter but cannot be used as the type of the return value
// 定义一个支持逆变的类
class Runoob<in A>(a: A) {
fun foo(a: A) {
}
}
fun main(args: Array<String>) {
var strDCo = Runoob("a")
var anyDCo = Runoob<Any>("b")
strDCo = anyDCo
}
Star projection
Sometimes, you may want to indicate that you do not know any information about the type parameter, but still want to be able to use it safely. The so-called "safe use" here means that a type projection is defined for a generic type, and the generic type is required All entity instances of are subtypes of this projection.
For this problem, Kotlin provides a syntax called star-projection:
- If the type is defined as Foo<out T>, where T is a covariant type parameter, the upper bound is Tupper, and Foo< > is equivalent to Foo<out TUpper>. It means that when T is unknown, you It is safe to read a value of type Tupper from Foo< >.
- If the type is defined as Foo<in T>, where T is a reverse covariant type parameter, Foo< > is equivalent to Foo<inNothing>. It means that when T is unknown, you cannot safely write to Foo< > Into anything.
- If the type is defined as Foo<T>, where T is a covariant type parameter, and the upper bound is Tupper, for reading the value, Foo<*> is equivalent to Foo<out TUpper>, for writing When entering a value, it is equivalent to Foo<in Nothing>.
If there are multiple type parameters in a generic type, then each type parameter can be cast separately. For example, if the type is defined as interface Function<in T, out U>, then the following types of asterisk projections can appear:
- Function<*, String> , 代表 Function<in Nothing, String> ;
- Function<Int, *> , 代表 Function<Int, out Any?> ;
- Function<, > , 代表 Function<in Nothing, out Any?> .
Note: Asterisk projection is very similar to Java's raw type, but it can be used safely