转向Kotlin——泛型

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/jiashuai94/article/details/80341793

更多精彩内容,欢迎关注我的微信公众号——Android机动车

无论是Java还是Kotlin,泛型都是一个非常重要的概念,简单的泛型应用很容易理解,不过也有理解起来麻烦的时候。

泛型基础

在了解Kotlin的泛型之前,先来看看Java中的泛型:

举个栗子:在JDK中,有一类列表对象,这些对象对应的类都实现了List接口。List中可以保存任何对象:

List list=new ArrayList();
list.add(55);
list.add("hello");

上面的代码中,List中保存了Integer和String两种类型值。尽管这样做是可以保存任意类型的对象,但每个列表元素就失去了原来对象的特性,因为在Java中任何类都是Object的子类,这样做的弊端就是原有对象类型的属性和方法都不能再使用了。

但在定义List时,可以指定元素的数据类型,那么这个List就不再是通用的了,只能存储一种类型的数据。JDK1.5之后引入了一个新的概念:泛型。

所谓泛型,就是指在定义数据结构时,只指定类型的占位符,待到使用该数据结构时再指定具体的数据类型:

public class Box<T> {

    private T t;

    public Box(T t) {
        this.t = t;
    }
}


Box<Integer> box=new Box(2);

在Kotlin中同样也支持泛型,下面是Kotlin实现上面同样的功能:

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

var box: Box<String> = Box("haha")

类型变异

Java中

Java泛型中有类型通配符这一机制,不过在Kotlin泛型中,没有通配符。

先看一个Java的栗子:

List<String> list1 = new ArrayList<>();
List<Object> list2 = list1;  // 编译错误

以上代码编译错误。这里有两个List对象,很明显String是Object的子类,但遗憾的是,Java编译器并不认为List < String >和List < Object> 有任何关系,直接将list1赋值给list2是会编译报错的,这是由于List的父接口是Collection:

public interface Collection<E> extends Iterable<E> {..}

为了解决这个问题,Java泛型提供了问号(?)通配符来解决这个问题。例如Collection接口中的addAll方法定义如下:

boolean addAll(Collection<? extends E> var1);

? extends E 表示什么呢,表示任何父类是E(或者E的任何子类和自己)都满足条件,这样就解决了List < String > 给List < Object> 赋值的问题。

出了extend还有super,这里不再过多介绍。

Kotlin中

Kotlin泛型并没有提供通配符,取而代之的是out和in关键字。用out声明的泛型占位符只能在获取泛型类型值得地方,如函数的返回值。用in声明的泛型占位符只能在设置泛型类型值的地方,如函数的参数。

我们习惯将只能读取的对象称为生产者,将只能设置的对象称为消费者。如果你使用一个生产者对象,将无法对这个对象调用add或set等方法,但这并不代表这个对象的值是不变的。例如,你完全可以调用clear方法来删除List中的所有元素,因为clear方法不需要任何参数。

通配符类型(或者其他任何的类型变异),唯一能够确保的仅仅是类型安全

abstract class Source<out T> {
    abstract fun func(): T
}

abstract class Comparable<in T> {
    abstract fun func(t: T)
}

类型投射

如果将泛型类型T声明为out,就可以将其子类化(List < String > 是List < Object> 的子类型),这是非常方便的。如果我们的类能够仅仅只返回T类型的值,那么的确可以将其子类化。但如果在声明泛型时未使用out声明T呢?

现在有一个Array类如下:

class Array<T>(val size: Int) {
    fun get(index: Int): T {
    }

    fun set(index: Int, t: T) {
    }
}

此类中的T既是get方法的返回值,又是set方法的参数,也就是说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]
    }
}

这个copy方法,就是将一个Array复制到另一个Array中,现在尝试使用一下:

val ints: Array<Int> = arrayOf(1, 2, 3)
val any: Array<Any> = Array(3)
copy(ints, any)  // 编译错误,因为Array<Int> 不是Array<Any>的子类型

Array< T > 对于类型参数T是不可变的,因此Array< Int> 和Array< Any>他们没有任何关系,为什么呢?因为copy可能会进行一些不安全的操作,也就是说,这个函数可能会试图向from中写入数据,这样可能会抛类型转换异常。

可以这样:

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

将from的泛型使用out修饰。

这种声明在Kotlin中称为类型投射:from不是一个单纯的数组,而是一个被限制(投射)的数组,我们只能对这个数组调用那些返回值为类型参数T的函数,在这个例子中,我们只能调用get方法,这就是我们事先使用处的类型变异的方案。

in关键字也是同理。

泛型函数

不仅类可以有泛型参数,函数一样可以有泛型参数。泛型参数放在函数名称之前

fun <T> getList(item: T): List<T> {
    ...
}

调用泛型函数时,应该在函数名称之后指定调用端类型参数。

val value = getList<Int>(1)

泛型约束

对于一个给定的泛型参数,所允许使用的类型,可以通过泛型约束来限制,最常见的约束是上界,与Java中的extends类似。

fun <T : Any> sort(list: List<T>) {

} 

冒号之后指定的类型就是泛型参数的上界:对于泛型参数T,允许使用Any的子类型。如果没有指定,则默认使用的上界类型是“Any?”,在定义泛型参数的尖括号内,值允许定义唯一一个上界。

小结

Kotlin泛型是在Java泛型的基础上进行了改进,变得更好用,更安全,尽管上述的泛型技术不一定都用得上,但对于全面了解Kotlin泛型会起到很大作用。

更多精彩内容,欢迎关注我的微信公众号——Android机动车
这里写图片描述

阅读更多
想对作者说点什么?

博主推荐

换一批

没有更多推荐了,返回首页