这里有新鲜出炉的精品教程,程序狗速度看过来!
Kotlin 是一个基于 JVM 的新的编程语言,由 JetBrains 开发。
这篇文章主要介绍了 Kotlin 泛型详解及简单实例的相关资料, 需要的朋友可以参考下
Kotlin 泛型详解
概述
一般类和函数,只能使用具体的类型:要么是基本类型,要么是自定义的类。如果要编写可以应用于多种类型的代码,这种刻板的约束对代码的限制很大。而 OOP 的多态采用了一种泛化的机制,在 SE 5 种,Java 引用了泛型。泛型,即 "参数化类型"。一提到参数,最熟悉的就是定义方法时有形参,然后调用此方法时传递实参。那么参数化类型怎么理解呢?顾名思义,就是将类型由原来的具体的类型参数化,类似于方法中的变量参数,此时类型也定义成参数形式(可以称之为类型形参),然后在使用 / 调用时传入具体的类型(类型实参)。
在 Kotlin 中,依然可以使用泛型,解耦类与函数与所用类型之间的约束,甚至是使用方法都与 Java 一致。
泛型类
声明一个泛型类
- class Box < T > (t: T) {
- var value = t
- }
通常, 要创建这样一个类的实例, 我们需要指定类型参数:
- val box: Box < Int > =Box < Int > (1)
但是, 如果类型参数可以通过推断得到, 比如, 通过构造器参数类型, 或通过其他手段推断得到, 此时允许省略类型参数:
- val box = Box(1) // 1 的类型为 Int, 因此编译器知道我们创建的实例是 Box<Int> 类型
泛型函数
泛型函数与其所在的类是否是泛型没有关系。泛型函数使得该函数能够独立于其所在类而产生变化。在 <Thinking in Java> 有这么一句话:无论何时只要你能做到,你就应该尽量使用泛型方法,也就是说如果使用泛型方法可以取代将整个类泛型化,那么就应该只使用泛型方法,因为它可以使事情更明白。这种泛型使用思想,在 Kotlin 中依然可以延续。
下面我们声明了一个泛型函数 doPrintln,当 T 是一个 Int 类型时,打印其个位的值;如果 T 是 String 类型,将字母全部大写输出;如果是其他类型,打印 "T is not Int and String"。
- fun main(args: Array<String>) {
- val age = 23
- val name = "Jone"
- val person = true
- doPrintln(age) // 打印:3
- doPrintln(name) // 打印:JONE
- doPrintln(person) // 打印:T is not Int and String
- }
- fun <T> doPrintln(content: T) {
- when (content) {
- is Int -> println(content % 10)
- is String -> println(content.toUpperCase())
- else -> println("T is not Int and String")
- }
- }
注:
擦除的神秘之处
下面我们先看一段代码:
- class Box<T>(t : T) {
- var value = t
- }
- fun main(args: Array<String>) {
- var boxInt = Box<Int>(10)
- var boxString = Box<String>("Jone")
- println(boxInt.javaClass) // 打印:class com.teaphy.generic.Box
- println(boxString.javaClass) // 打印:class com.teaphy.generic.Box
- }
现声明了一个泛型类 Box<T>, 在不同的类型的类型在行为方面肯定不一样,但是在我们获取其所在类时,我们只是得到了 "class com.teaphy.generic.Box"。在这里我们不得不面对一个残酷的现实:在泛型内部,无法获得任何有关泛型参数类型的信息。
不管是 Java 还是 Kotlin,泛型都是使用擦除来实现的,这意味着当你在使用泛型时,任务具体的类型信息都被擦除的,你唯一知道的就是你再使用一个对象。比如,Box<String> 和 Box<Int> 在运行时是想的类型,都是 Box 的实例。在使用泛型时,具体类型信息的擦除是我们不不懂得不面对的,在 Kotlin 中也为我们提供了一些可供参考的解决方案:
类型协变
在类型声明时,使用协变注解修饰符 (in 或者 out)。于这个注解出现在类型参数的声明处, 因此我们称之为声明处的类型变异。如果在使用泛型时,使用了该类型编译了会有什么效果呢?
假设我们有一个泛型接口 Source<in T, out R>, 其中 T 由协变注解 in 修饰,R 由协变注解 Out 修饰.
- internal interface Source < inT,
- out R > {
- fun mapT(t: T) : Unit fun nextR() : R
- }
从上面的解释中,我们可以清楚的知道了协变注解 in 和 out 的用意,其实际上是定义了类型参数在该类或者接口的用途,是用来消费的还是用来返回的,对其做了相应的限定。
类型投射
上面我们已经了解到了协变注解 in 和 out 的用意,下面我们将会用 in 和 out,做一件有意义的事,看下面代码
- fun copy(from: Array < out String > , to: Array < Any > ) {
- // ...
- }
- fun fill(dest: Array < inString > , value: String) {
- // ...
- }
对于 copy 函数中中,from 的泛型参数使用了协变注解 out 修饰,意味着该参数不能在该函数中消费,也就是说在该函数中禁止对该参数进行任何操作。
对于 fill 函数中,dest 的泛型参数使用了协变注解 in 修饰,Array<in String> 与 Java 的 Array<? super String> 相同, 也就是说, 你可以使用 CharSequence 数组, 或者 Object 数组作为 fill() 函数的参数
这种声明在 Kotlin 中称为类型投射 (type projection),类型投射的主要用于对参数做了相对因的限定,避免了对该参数类的不安全操作。
星号投射
有些时候, 你可能想表示你并不知道类型参数的任何信息, 但是仍然希望能够安全地使用它. 这里所谓 "安全地使用" 是指, 对泛型类型定义一个类型投射, 要求这个泛型类型的所有的实体实例, 都是这个投射的子类型.
对于这个问题, Kotlin 提供了一种语法, 称为 星号投射 (star-projection):
如果一个泛型类型中存在多个类型参数, 那么每个类型参数都可以单独的投射. 比如, 如果类型定义为 interface Function<in T, out U> , 那么可以出现以下几种星号投射:
注意: 星号投射与 Java 的原生类型 (raw type) 非常类似, 但可以安全使用
泛型约束
对于一个给定的类型参数, 所允许使用的类型, 可以通过泛型约束 (generic constraint) 来限制。
上界
最常见的约束是 上界 (upper bound):
- fun < T: Comparable < T >> sort(list: List < T > ) {
- // ...
- }
冒号之后指定的类型就是类型参数的 上界 (upper bound): 对于类型参数 T , 只允许使用 Comparable<T> 的子类型. 比如:
- sort(listOf(1, 2, 3)) // 正确: Int 是 Comparable<Int> 的子类型
- sort(listOf(HashMap<Int, String>())) // 错误: HashMap<Int, String> 不是
- Comparable<HashMap<Int, String>> 的子类型
如果没有指定, 则默认使用的上界是 Any? . 在定义类型参数的尖括号内, 只允许定义唯一一个上界. 如果同一个类型参数需要指定多个上界, 这时就需要使用单独的 where 子句:
- fun <T> cloneWhenGreater(list: List<T>, threshold: T): List<T> where T : Comparable,
- T : Cloneable {
- return list.filter { it > threshold }.map { it.clone() }
- }
来源: http://www.phperz.com/article/17/0822/338441.html