Generics, or “parameterized types”, parameterize types and can be used on classes, interfaces, and methods.
Same as Java
Kotlin
generics are also provided to ensure type safety and eliminate the annoyance of type overturning.
Declare a generic class:
class Box<T>(t: T) {
var value = t
}
We need to specify the type parameters when creating an instance of the class:
val box: Box<Int> = Box<Int>(1)
// or
val box = Box(1) // The compiler will perform type inference, type 1 is Int,
so the compiler knows that we are referring to Box<Int>.
The following example gives a generic class
Box
pass in integer data and strings:
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 is as follows:
10
Runoob
By defining generic type variables, you can fully specify the type parameters, and if the compiler can automatically deduce the type parameters, you can also omit them.
Kotlin
declaration of generic functions and
Java
similarly, the type parameter should be placed before the function name:
fun <T> boxIn(value: T) = Box(value)
// The following are all legal statements
val box4 = boxIn<Int>(1)
val box5 = boxIn(1) // The compiler will perform type inference
When calling a generic function, if you can infer the type parameters, you can omit the generic parameters.
The following example creates a generic function
doPrintln
. The functionshould be handled accordingly according to the different types passed in:
fun main(args: Array<String>) {
val age = 23
val name = "runoob"
val bool = true
doPrintln(age) // integer
doPrintln(name) // character string
doPrintln(bool) // Boolean type
}
fun <T> doPrintln(content: T) {
when (content) {
is Int -> println("Integer number is $content")
is String -> println("Convert string to uppercase:${content.toUpperCase()}")
else -> println("T is not an integer or a string")
}
}
The output is as follows:
The integer number is 23
Convert string to uppercase: RUNOOB
T is not an integer or a string
3.15.1. Generic constraint #
We can use generic constraints to set the type allowed for a given parameter.
Kotlin
use in: constrains the type upper limit of generics.
The most common constraint is the upper bound:
fun <T : Comparable<T>> sort(list: List<T>) {
// ……
}
Comparable
can be replaced by the subtype of
T
. For example:
sort(listOf(1, 2, 3)) // OK。Int is the subtype of Comparable<Int>
sort(listOf(HashMap<Int, String>())) // error:HashMap<Int, String>
is not the subtype of 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() }
}
3.15.2. Shape change #
There are no wildcard types in
Kotlin
, it has two other things: declaration-site variance at the declaration point and type projection (typeprojections).
3.15.3. Declaration place type change #
The type variation at the declaration uses the covariant annotation modifier:
in
、
out
consumers
in
producer
out
.
Use
out
to make a type parameter covariant, the covariant type parameter can only be used as an output, can be used as a return value type,but cannot be used as an input parameter:
// Define a class that supports covariation
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()) // Output a
}
So that a type parameter
in
is inverted, and the inverter type parametercan only be used as input, and can be used as the type of input parameter but not as the type of return value:
// Define a class that supports inversion
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
}
3.15.4. Asterisk projection #
Sometimes, you may want to show that you don’t know any information about type parameters, 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, requiring that all entity instances of the generic type are subtypes of this projection.
As for this question,
Kotlin
provides a syntax called asterisk projection (star-projection):
If the type is defined as
Foo<out T>, where T is a covariant type parameter with an upper bound ofTUpper, andFoo<*>is equivalent toFoo<out TUpper>. It means that when T is unknown, you can safely read values of typeTUpperfromFoo<*>.If the type is defined as
Foo<in T>where T is a reverse covariant typeparameterFoo<*>equivalent toFoo<inNothing>. It means that when T is unknown, you cannot safely report toFoo<*>.If the type is defined as
Foo<T>where T is a covariant type parameter and the upper bound (upper bound) isTUpperfor the case of reading valuesFoo<*>equivalent toFoo<out TUpper>in the case of writinga value, it is equivalent toFoo<in Nothing>.
If there are multiple type parameters in a generic type, each type parametercan be projected separately. For example, if the type is defined as
interface
Function<in
T,
out
U>
, then the following asterisk projections can occur:
Function<*, String>,representativeFunction<in Nothing, String>;Function<*, String>,representativeFunction<in Nothing, String>;Function< *,* >, representativeFunction<in Nothing, out Any?>.
Note: asterisk projection is very similar to Java’s native type (raw type), but can be used safely