Kotlin基础之泛型

来源:互联网 发布:用友数据库重装 编辑:程序博客网 时间:2024/06/01 07:28

泛型

与Java一样,Koltin的类也有类型参数。例如:

class Box<T>(t: T){    var value = t}

常规来说,创建这样的类,需要提供具体的类型。例如:

val box: Box<Int> = Box<Int>(1)

当类型可以从构造参数或其他上下文中推断出时,可以忽略类型参数。上面的代码可以简化为:

val box = Box(1)

型变

Java类型系统中最复杂的其中一个部分就是通配符类型(Java泛型FAQ)。而Kotlin没有任何的通配符类型,它使用声明处变型和类型投影两种方式替代。

通配符 - 使用问号表示的类型参数,表示未知类型的类型约束方法。

首先,先思考为什么Java需要这些难以理解的通配符。Effective Java解释了这个问题,第28条:使用受限通配符来增加API灵活性。首先,Java中泛型为不可变类型,意味List不是List的子类型。为什么这样?如果List为可变量,List<>不会比Java的数组更好,并且下面的代码能够成功编译,但在运行时会引起异常。

// JavaList<String> strs = new ArrayList<String>();List<Object> objs = strs; // 会引起错误,Java禁止这样使用。objs.add(1);  // Here we put an Integer into a list of StringsString s = strs.get(0); // 类转换异常:无法将Integer转换为String

所以Java禁止这样做,目的是保证运行时安全。但会有一些影响。比如:Collection接口的addAll()方法,这个方法的签名是什么?我们直觉上会这样做:

// Javainterface Collection<E> ... {  void addAll(Collection<E> items);}

考虑到运行时安全,我们无法做到像下面的简单操作。

// Javavoid copyAll(Collection<Object> to, Collection<String> from) {  to.addAll(from); // !!! Would not compile with the naive declaration of addAll:                   //       Collection<String> is not a subtype of Collection<Object>}

实际上,addAll()的方法签名是:

// Javainterface Collection<E> ... {  void addAll(Collection<? extends E> items);}

通配符参数? extends E表明方法接收类型为E的子类集合,而非E本身。意味着可以安全读取集合中为E的值(集合的元素类型为E的子类实例),但无法写入E,因为我们不知道对象是否是E未知的子类。作为交换,我们希望得到这些行为:Collection为Collection

声明处变型

假设现有一个Source泛型接口,没有使用T作为参数的方法,只有一个返回T的方法。例如:

// Javainterface Source<T> {  T nextT();}

那么使用Source的变型来存储Source实例引用是类型安全的(因为没有消费者方法)。但是Java仍会禁止这样做,下面的代码是不合法的。

// Javavoid demo(Source<String> strs) {  Source<Object> objects = strs; // !!! Not allowed in Java  // ...}

当然,可以声明Source

abstract class Source<out T> {    abstract fun nextT(): T}fun demo(strs: Source<String>) {    val objects: Source<Any> = strs // This is OK, since T is an out-parameter    // ...}

泛型规则:当类C的泛型参数T声明为out时,表示T只能出现在C成员的输出位置,作为交换,C是C类型安全的超类。

称类C是参数T的协变量,或T是协变量类型参数。可以认为类C是T的生产者,而不是T的消费者。

out修饰符称为变型注解,因为它提供了类型参数声明点,因此称之为声明点类型。

除了out,kotlin提供了一个补充的变型注解:in。让类型参数变为逆变量:只能消费,从不生产。Comparable就是协变量一个很好的例子。

abstract class Comparable<in T> {    abstract fun compareTo(other: T): Int}fun demo(x: Comparable<Number>) {    x.compareTo(1.0) // 1.0 has type Double, which is a subtype of Number    // Thus, we can assign x to a variable of type Comparable<Double>    val y: Comparable<Double> = x // OK!}

类型投影

使用处变型:类型投影 声明类型参数T为out很方便,避免在使用处子类型化。但一些类实际时无法限制只返回T,Array就是一个很好的例子:

class Array<T>(val size: Int){    fun get(index: Int): T{ /*...*/}    fun set(index: Int , value: T){/*...*/}}

Array类既不是T的协变,也不是T的逆变,导致不够灵活。考虑下面的函数:

fun copy(from: Array<Any> , to: Array<Any>){    assert(from.size == to.size)    for(i in from.indices)        to[i] = from[i]}

函数应该是从拷贝数组中数据到另一个数组,下面将函数用在实际中:

val ints: Array<Int> = arrayOf(1, 2, 3)val any = Array<Any>(3){ ""}copy(ints , ant) // 错误:expects(Array<Any>, Array<Any>)

遇到了相同的问题:Array是不变的,T类型的数组,所以Array和Array都不是对方的子类。因为copy可能会坏事,可能会进行写操作,比如像from写入String,而实际上这里传入的是Int数组,运行时就能出现ClassCastException异常。

因此,只需要保证copy不会做坏事,禁止向from写数据,可以这样做:

fun copy(from: Array<out Any> , to: Array<Any>){    //...}

这样做法被称为类型投影(type projection),也是说from不是一个简单数组,而是受限(投影)类型:只能够调用那些返回类型为T的方法,在这种情况意味着只能调用get,这也是使用使用出变型的目的,对应java的Array

fun fill(dest: Array<in String> , value: String){    // ...}

Array对应Java的Array

星号投影

有时不知道类型参数任何信息,但仍希望安全地使用。此时安全地定义投影的泛型,每个泛型的具体实例都是泛型的子类型。 为此,Kotlin提供称为星号投影的语法。

  • 对于Foo,T为带有上界TUpper的协变量,Foo<>等价于Foo。意味着T类型未知时,可以安全地读取Foo<>中TUpper的值
  • 对于Foo,T为逆变类型参数,Foo<>等价于Foo,意味着当T类型未知时,无法安全写入Foo<>
  • 对于Foo,T为不可变类型参数,带有上界TUpper,Foo<*>等价于Foo用于读取和Foo用于写入值。
    如果泛型有多个类型参数,则每个都可以独立投影。比如,如果类型声明为interface Function

泛型函数

不仅类可以有类型参数,函数也可以有。函数的类型参数在函数名之前声明:

fun <T> SingletonList(item: T ): List<T>{    // ...}fun <T> T.basicToString() : String { // 扩展函数    // ...}

调用泛型函数,在调用的函数名之后指定具体类型参数。

val l = SingletonList<Int>(1)

泛型约束

所有可以被指定类型参数替代的类型,都可以使用泛型约束进行限制。

上界

最常见的泛型约束就是上界,对应java的extends关键字。

fun <T : Comparable<T>> sort(list: List<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()}              }

附:里氏替换原则协变与逆变泛型中的协变和逆变

阅读全文
'); })();
0 0
原创粉丝点击
热门IT博客
热门问题 老师的惩罚 人脸识别 我在镇武司摸鱼那些年 重生之率土为王 我在大康的咸鱼生活 盘龙之生命进化 天生仙种 凡人之先天五行 春回大明朝 姑娘不必设防,我是瞎子 天津医科大学临床医学院 同型半胱氨酸临床意义 安徽医科大学临床医学院 糖化血红蛋白临床意义 河北医科大学临床学院 横纹肌溶解症临床表现 血气分析正常值和临床意义 临建活动房 速写临摹 临摹 临摹画 临摹读音 临摹字帖 素描临摹 临摹素材 临摹的意思 书法临摹 静物临摹 临摹图片 什么是临摹 临摹拼音 毛笔字临摹 钢笔字临摹 练字临摹 水彩临摹 临摹本 水彩画临摹 油画临摹 行书临摹 楷书临摹 国画临摹 字帖临摹 兰亭序临摹 行楷临摹 色彩临摹 数位板临摹 什么叫临摹 临摹素描 硬笔临摹 碑帖临摹 毛笔临摹