Kotlin型变


Java的泛型不支持型变,Java采用通配符来解决这个问题;Kotlin采用安全的型变代替了Java的通配符。

一、泛型型变的需要

Java泛型的通配符语法默认是有上限的,只不过其上限是Object类。

泛型存在如下规律:

  • 通配符上限(泛型协变)意味着从中取出(out)对象是安全的,但传入对象(in)则不可靠。
  • 通配符下限(泛型逆变)意味着向其中传入(in)对象是安全的,但取出对象(out)则不可靠。

Kotlin利用上面两个规律,抛弃了泛型通配符语法,而是利用in、out来让泛型支持型变。

二、声明处型变

Kotlin处理泛型型变的规则如下:

  • 如果泛型只需要出现在方法的返回值声明中(不出现在形参声明中),那么该方法就只是取出泛型对象,因此该方法就支持泛型协变(相当于通配符上限);如果一个类的所有方法都支持泛型协变,那么该类的泛型参数可使用out修饰。
  • 如果泛型只需要出现在方法的形参声明中(不出现在返回值声明中),那么该方法就只是传入泛型对象,因此该方法就支持泛型逆变(相当于通配符下限);如果一个类的所有方法都支持泛型逆变,那么该类的泛型参数可使用in修饰。

下例中定义一个支持泛型协变的类。

package test0709

class User<out T> {
    //此处不能用var,否则就有setter方法
    //setter方法会导致T出现在方法形参中
    val info: T

    constructor(info: T) {
        this.info = info
    }

    fun test(): T {
        println("执行test方法")
        return info
    }
}

fun main(args: Array<String>) {
    //此时T的类型是String
    var user = User<String>("Kotlin")
    println(user.info)
    //对于u2而言,它的类型是User<Any>,此时T的类型是Any
    //由于程序声明了T支持协变,因此User<String>可当成User<Any>使用
    var u2: User<Any> = user
    println(u2.info)
}

输出结果:

Kotlin
Kotlin

下例中定义一个支持泛型逆变的类。

package test0709

class Item<in T> {
    fun foo(t: T) {
        println(t)
    }
}

fun main(args: Array<String>) {
    //此时T的类型是Any
    var item = Item<Any>()
    item.foo(20)
    var im2:Item<String> = item
    //im2的实际类型是Item<Any>,因此它的参数只要是Any即可
    //声明了im2的类型为Item<String>
    //因此传入的参数只可能是String
    im2.foo("Kotlin")
}

输出结果:

20
Kotlin
  • 如果泛型T只出现在该类的方法的返回值声明中,那么该类泛型形参即可使用out修饰T。
  • 如果泛型T只出现在该类的方法的形参声明中,那么该泛型形参即可使用in修饰T。

上面的类都是在声明时使用out或in指定泛型支持型变的,这种方式被称为“声明处型变”。

三、使用处型变:类型投影

如果不能使用声明处型变,则可以使用Kotlin提供的“使用处型变”。所谓使用处型变,就是在使用泛型时对其使用out或in修饰。

下例为使用处协变:

package test0709

fun copy(from: Array<out Any>, to: Array<Any>) {
    val len = if (from.size < to.size) from.size else to.size
    for (i in 0 until len) {
        to[i] = from[i]
    }
}

fun main(args: Array<String>) {
    var arr1 = arrayOf(2, 3, 5, 7)
    var arr2: Array<Any> = arrayOf(4, 12, 15, 19, 100)
    copy(arr1, arr2)
    println(arr2.contentToString())
}

输出结果:

[2, 3, 5, 7, 100]

下例为使用处逆变:

package test0709

fun fill(dest: Array<in String>, value: String) {
    if (dest.size > 0) {
        dest[0] = value
    }
}

fun main(args: Array<String>) {
    var arr1: Array<CharSequence> = arrayOf("a", "b", StringBuilder("cc"))
    fill(arr1, "Kotlin")
    println(arr1.contentToString())

    var intArr: Array<in Int> = arrayOf(2, 5, 39)
    println(intArr.contentToString())
    intArr.set(0, 34)

    var numArr: Array<Number> = arrayOf(3, 4.1, 10.4)
    intArr = numArr
    println(intArr.contentToString())
}

输出结果:

[Kotlin, b, cc]
[2, 5, 39]
[3, 4.1, 10.4]

Array相当于Java的泛型下限:Array<? super Int>,因此这种泛型型变用于支持逆变,还可以保证向其中安全地添加元素。

四、星号投影

星号投影是为了处理Java的原始类型。

fun main(args: Array<String>) {
    //<*>必不可少,相当于Java的原始类型
    var list: ArrayList<*> = arrayListOf(1, "Kotlin")
    println(list)
}

输出结果:

[1, Kotlin]

如果泛型类型具有多个类型参数,那么每个类型参数都可以单独指星号投影。

假如声明了支持两个泛型参数的Foo<in T,out U>类型,关于星号投影的解释如下:

  • 对于Foo<*,String>,相当于Foo<in Nothing,String>
  • 对于Foo<Int,*>,相当于Foo<Int,out Any?>
  • 对于Foo<*,*>,相当于Foo<in Nothing,out Any?>

学海无涯苦作舟

我的微信公众号.jpg


文章作者: HunterArley
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 HunterArley !
评论
  目录