转向Kotlin——泛型

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

泛型基础

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

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

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

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

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

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

类型变异

Java中

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

先看一个Java的栗子:

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

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

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

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

Kotlin中

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

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

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

类型投射

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

现在有一个Array类如下:

此类中的T既是get方法的返回值,又是set方法的参数,也就是说Array类既是T的生产者,也是T的消费者,这样的类就无法进行子类化。

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

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

可以这样:

将from的泛型使用out修饰。

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

in关键字也是同理。

泛型函数

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

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

泛型约束

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

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

小结

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

  • 发表于:
  • 原文链接https://kuaibao.qq.com/s/20180521G08TNE00?refer=cp_1026
  • 腾讯「云+社区」是腾讯内容开放平台帐号(企鹅号)传播渠道之一,根据《腾讯内容开放平台服务协议》转载发布内容。

扫码关注云+社区

领取腾讯云代金券