转向Kotlin——泛型

更多精彩内容,欢迎关注个人微信公众号——Android机动车数组

不管是Java仍是Kotlin,泛型都是一个很是重要的概念,简单的泛型应用很容易理解,不过也有理解起来麻烦的时候。安全

泛型基础

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

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

List list=new ArrayList();
list.add(55);
list.add("hello");
复制代码

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

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

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

public class Box<T> {
    
    private T t;

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


Box<Integer> box=new Box(2);
复制代码

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

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

var box: Box<String> = Box("haha")
复制代码

类型变异

Java中

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

先看一个Java的栗子:code

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机动车

这里写图片描述
相关文章
相关标签/搜索