通配符类型参数? extends E 表示此方法接受 E或者 E 的一个子类型对象的集合,而不只是 E 自身。 这意味着我们可以安全地从其中
(该集合中的元素是 E 的子类的实例)读取E,但不能写入,
因为我们不知道什么对象符合那个未知的 E 的子类型。
反过来,该限制可以得到想要的行为:Collection 表示为 Collection extends Object> 的子类型。
简而言之,带 extends 限定(上界)的通配符类型使得类型是协变的(covariant)。
理解为什么这能够工作的关键相当简单:如果只能从集合中获取元素,
那么使用 String 的集合, 并且从其中读取 Object 也没问题 。反过来,如果只能向集合中
放入 元素 , 就可以用 Object 集合并向其中放入 String:in Java there is
List super String>, which accepts Strings or any of its supertypes.
后者称为逆变性(contravariance),并且对于 List super String> 你只能调用接受 String 作为参数的方法
(例如,你可以调用 add(String) 或者 set(int, String)),如果调用函数返回 List 中的 T,
你得到的并非一个 String 而是一个 Object。
interface Comparable {
operator fun compareTo(other: T): Int
}
fun demo(x: Comparable) {
x.compareTo(1.0) // 1.0 拥有类型 Double,它是 Number 的子类型
// 因此,可以将 x 赋给类型为 Comparable 的变量
val y: Comparable = x // OK!
}
in 和 out 两词看起来是自解释的(因为它们已经在 C# 中成功使用很长时间了),
因此上面提到的助记符不是真正需要的。可以将其改写为更高级的抽象:
fun copyWhenGreater(list: List, threshold: T): List
where T : CharSequence,
T : Comparable {
return list.filter { it > threshold }.map { it.toString() }
}
所传递的类型必须同时满足 where 子句的所有条件。在上述示例中,类型 T 必须
既实现了 CharSequence也实现了 Comparable。
Definitely non-nullable types
To make interoperability with generic Java classes and interfaces easier, Kotlin supports declaring a generic type parameter
as definitely non-nullable.
To declare a generic type T as definitely non-nullable, declare the type with & Any. For example: T & Any.
A definitely non-nullable type must have a nullable upper bound.
The most common use case for declaring definitely non-nullable types is when you want to override a Java method that
contains @NotNull as an argument. For example, consider the load() method:
import org.jetbrains.annotations.*;
public interface Game {
public T save(T x) {}
@NotNull
public T load(@NotNull T x) {}
}
To override the load() method in Kotlin successfully, you need T1 to be declared as definitely non-nullable:
interface ArcadeGame : Game {
override fun save(x: T1): T1
// T1 is definitely non-nullable
override fun load(x: T1 & Any): T1 & Any
}
When working only with Kotlin, it's unlikely that you will need to declare definitely non-nullable types explicitly because
Kotlin's type inference takes care of this for you.
由于类型擦除,并没有通用的方法在运行时检测一个泛型类型的实例是否通过指定类型参数所创建
,并且编译器禁止这种 is 检测,例如
ints is List or list is T (type parameter). 当然,你可以对一个实例检测星投影的类型:
if (something is List<*>) {
something.forEach { println(it) } // 每一项的类型都是 `Any?`
}
类似地,当已经让一个实例的类型参数(在编译期)静态检测,
就可以对涉及非泛型部分做 is 检测或者类型转换。请注意,
在这种情况下,会省略尖括号:
fun handleStrings(list: MutableList) {
if (list is ArrayList) {
// `list` 智能转换为 `ArrayList`
}
}
省略类型参数的这种语法可用于不考虑类型参数的类型转换:list as ArrayList。
泛型函数调用的类型参数也同样只在编译期检测。在函数体内部,
类型参数不能用于类型检测,并且类型转换为类型参数(foo as T)也是非受检的。
The only exclusion is inline functions with reified type parameters,
which have their actual type arguments inlined at each call site. This enables type checks and casts for the type parameters.
However, the restrictions described above still apply for instances of generic types used inside checks or casts.
For example, in the type check arg is T, if arg is an instance of a generic type itself, its type arguments are still erased.
类型转换为带有具体类型参数的泛型类型,如 foo as List 无法在运行时检测。
当高级程序逻辑隐含了类型转换的类型安全而无法直接通过编译器推断时,
可以使用这种非受检类型转换。 See the example below.
fun readDictionary(file: File): Map = file.inputStream().use {
TODO("Read a mapping of strings to arbitrary elements.")
}
// 我们已将存有一些 `Int` 的映射保存到这个文件
val intsFile = File("ints.dictionary")
// Warning: Unchecked cast: `Map` to `Map`
val intsDictionary: Map = readDictionary(intsFile) as Map
The underscore operator _ can be used for type arguments. Use it to automatically infer a type of the argument when other types are explicitly specified:
abstract class SomeClass {
abstract fun execute() : T
}
class SomeImplementation : SomeClass() {
override fun execute(): String = "Test"
}
class OtherImplementation : SomeClass() {
override fun execute(): Int = 42
}
object Runner {
inline fun , T> run() : T {
return S::class.java.getDeclaredConstructor().newInstance().execute()
}
}
fun main() {
// T is inferred as String because SomeImplementation derives from SomeClass
val s = Runner.run()
assert(s == "Test")
// T is inferred as Int because OtherImplementation derives from SomeClass
val n = Runner.run()
assert(n == 42)
}