天天看点

《Kotlin从小白到大牛》第15章:泛型

第15章 泛型

使用泛型可以最大限度地重用代码、保护类型的安全以及提高性能。泛型特性对Kotlin影响最大是在集合中使用泛型。本章详细介绍使用泛型。

15.1 泛型函数

泛型可以应用于函数声明、属性声明、泛型类和泛型接口,本节介绍泛型函数。

15.1.1 声明泛型函数

首先考虑一个问题,怎样声明一个函数来判断两个参数是否相等呢?如果参数是Int类型,则函数声明如下:

private fun isEqualsInt(a: Int, b: Int): Boolean {

return (a == b)

}

这个函数参数列表是两个Int类型,它只能比较两个Int类型参数是否相等。如果想比较两个Double类型是否相等,可以修改上面声明的函数如下:

private fun isEqualsDouble(a: Double, b: Double): Boolean

{

return (a == b)

}

这个函数参数列表是两个Double类型,它只能比较两个Double类型参数是否相等。如果想比较两个String类型是否相等,可以修改上面声明的函数如下:

private fun isEqualsString(a: String, b: String): Boolean

{

return (a == b)

}

以上分别对3种类型的两个参数进行了比较,声明了类似的3个函数。那么是否可以声明一个函数使之能够比较3种类型呢?合并后的代码:

private fun isEquals(a: T, b: T): Boolean {

return (a == b)

}

在函数名isEquals前面添加这就是泛型函数了,是声明类型参数,T是类型参数,函数中参数类型也被声明为T,在调用函数时T会用实际的类型替代。

《Kotlin从小白到大牛》第15章:泛型

调用泛型函数代码如下:

fun main(args: Array) {

println(isEquals(1, 5))

println(isEquals(1.0, 5.0))

}

isEquals(1, 5)调用函数时类型参数T替换为Int类型,而isEquals(1.0, 5.0)调用函数时类型参数T替换为Double类型。

15.1.2 多类型参数

上一节泛型函数示例中只是使用了一种类型参数,事实上可以同时声明使用多个类型参数,它们之间用逗号“,”分隔,示例如下:

fun <T, U> addRectangle(a: T, b: U): Boolean {…}

类型参数不仅可以声明函数参数类型,还可以声明函数的返回类型,示例代码如下:

fun <T, U> rectangleEquals(a: T, b: U): U {…}

15.1.3 泛型约束

在15.1.1节声明的fun

isEquals(a: T, b: T): Boolean函数事实上还有一点问题,这是因为并不是所有的类型参数T都具有“可比性”,必须限定T的类型,如果只是数字类型比较可以限定为Number,因为Int和Double等数字类型都继承Number,是Number的子类型。声明类型参数时在T后面添加冒号(:)和限定类型,这种表示方式称为“泛型约束”,泛型约束主要应用于泛型函数和泛型类的声明。

示例代码如下:

//代码文件:chapter15/src/com/a51work6/section1/ch15.1.3.kt

package com.a51work6.section1

private fun isEquals(a: T, b: T):Boolean { ①

return (a == b)

}

fun main(args: Array) {

println(isEquals(1, 5)) //false ②

println(isEquals(1.0, 1.0)) //true ③

}

上述代码第①行是声明泛型函数,其中是带有约束的类型参数。代码第②行是比较两个Int整数是否相等,代码第③行是比较两个Double浮点数是否相等。

代码第①行的isEquals函数只能比较Number类型的参数,不能比较String等其他数据类型,为此也可以将类型参数限定为Comparable接口类型,所有可比较的对象都实现Comparable接口,Comparable本身也是泛型类型。

修改代码如下:

//代码文件:chapter15/src/com/a51work6/section1/ch15.1.3.kt

package com.a51work6.section1

import java.util.*

fun <T : Comparable> isEquals(a: T, b: T): Boolean {

return (a == b)

}

fun main(args: Array) {

println(isEquals(1, 5)) //false

println(isEquals(1.0, 1.0)) //true

println(isEquals(“a”,“a”)) //true ①

val d1 = Date()

val d2 = Date()

println(isEquals(d1, d2)) //true ②

}

代码第①行是比较两个字符串是否相等,代码第②行是比较两个日期是否相等。

15.1.4 可空类型参数

在泛型函数声明中,类型参数没有泛型约束,函数可以接收任何类型的参数,包括可空和非空数据。例如fun isEquals(a: T, b: T): Boolean函数调用时可以传递可空或非空数据,代码如下:

println(isEquals(null, 5)) //false

所有没有泛型约束的类型参数,事实上也是有限定类型的,只不过是Any?,Any?可以任何可空类型的根类,也兼容非空类型。

如果不想接收任何可空类型数据,可以采用Any作为约束类型,Any是任何非空类型的父类,代码如下:

private fun isEquals(a: T, b: T): Boolean

{ ①

return (a == b)

}

fun main(args: Array) {

println(isEquals(null, 5)) //编译错误 ②

println(isEquals(1.0, null)) //编译错误 ③

}

在代码第①行的isEquals函数中声明泛型约束类型限定为Any,所以代码第②行和第③行试图传递空值时发生编译错误。

15.2泛型属性

在Kotlin中还可以声明泛型属性,但是这种属性一定是扩展属性,不是能是普通属性。

《Kotlin从小白到大牛》第15章:泛型

示例代码如下:

//代码文件:chapter15/src/com/a51work6/section2/ch15.2.kt

package com.a51work6.section2

val ArrayList.first: T? //获得第一个元素 ①

get() = if (this.size > 1) this[0] else null

val ArrayList.second: T? //获得第二个元素 ②

get() = if (this.size > 2) this[1]else null

fun main(args: Array) {

val array1 = ArrayList<Int>()//等同于arrayListOf<Int>()    ③
println(array1.first)   //null
println(array1.second)  //null

val array2 = arrayListOf ("A","B", "C", "D")             ④
println(array2.first)   //A
println(array2.second)  //B
           

}

上述代码第①行和第②行是声明ArrayList集合的扩展属性first和second,其中使用了泛型。集合中的元素类型采用类型参数T表示,返回类型是T?表示可能有返回空值的情况。

代码第③行是实例化,Int类型的ArrayList集合,使用ArrayList构造函数创建一个空元素的集合对象。也可以使用arrayListOf()函数创建集合对象。代码是④行是创建String类型ArrayList集合对象,这里使用arrayListOf(“A”,

“B”, “C”, “D”)函数创建并初始化该集合。

15.3 泛型类

根据自己的需要也可以自定义泛型类和泛型接口。下面通过一个示例介绍一下泛型类。数据结构中有一种“队列”(queue)数据结构(如图15-1所示),它的特点是遵守“先入先出”(FIFO)规则。

《Kotlin从小白到大牛》第15章:泛型

本节通过自定义队列集合介绍任何实现泛型类。具体实现代码如下:

//代码文件:chapter15/src/com/a51work6/section3/Queue.kt

package com.a51work6.section3

import java.util.ArrayList

class Queue { ①

// 声明保存队列元素集合items

private val items:MutableList ②

// init初始化代码中实例化集合items

init {

this.items = ArrayList() ③

}

fun queue(item: T) { ④

this.items.add(item)

}

fun dequeue(): T? { ⑤

return if (items.isEmpty()) {

null

} else {

this.items.removeAt(0) ⑥

}

}

override fun toString(): String {

return items.toString()

}

}

上述代码第①行声明了Queue泛型类型的队列,是声明类型参数。代码第②行是声明一个MutableList泛型集合成员属性items,MutableList是可变数组接口,用来保存队列中的元素。代码第③行是init初始化代码,实例化ArrayList对象赋值给items属性。

代码第④行的queue是队列入队函数,其中参数item是要入队的元素,类型参数使用T表示。代码第⑤行的dequeue是出队函数,返回出队的那个元素,返回类型是T表示。在dequeue函数中首先判断集合是否有元素,如果没有元素返回空值;如果有元素则通过第⑥行this.items.remove(0)函数删除队列的第一个元素,并把删除的元素返回,以达到出队的目的。

调用队列示例代码如下:

//代码文件:chapter15/src/com/a51work6/section3/ch15.3.kt

package com.a51work6.section3

fun main(args: Array) {

val genericQueue =Queue<String>()          ①
genericQueue.queue("A")
genericQueue.queue("C")
genericQueue.queue("B")
genericQueue.queue("D")
//genericQueue.queue(1);//编译错误           ②

println(genericQueue)
genericQueue.dequeue()                            ③

println(genericQueue)
           

}

输出结果如下:

[A, C, B, D]

[C, B, D]

上述代码在使用了刚刚自定义的支持泛型的队列Queue集合。首先在代码第①行实例化Queue对象,通过尖括号指定限定的类型是String,这个队列中只能存放String类型数据。代码第②行试图向队列中添加整数1,则会发生编译错误。

代码第③行出队后操作,通过运行的结果可见,出队后第一个元素"A",会从中队列中删除。

在声明泛型类时也可以有多个类型参数,类似于泛型函数可以使用多个不同的字母声明不同的类型参数。另外,在泛型类中也可以使用泛型约束,如下代码所示:

class Queue {…}

15.4 泛型接口

不仅可以自定义泛型类还可以自定义泛型接口,泛型接口与泛型类声明的方式完全一样。下面将15.3节的示例修改称为队列接口,代码如下:

//代码文件:chapter15/src/com/a51work6/section4/IQueue.kt

package com.a51work6.section4

interface IQueue { ①

fun queue(item: T) ②

fun dequeue(): T? ③

}

上述代码声明了支持泛型的接口。代码第①行声明了IQueue泛型接口,T是类型参数。该接口中声明两个函数,代码第②行的queue函数是入队函数,类型参数使用T表示。代码第③行的dequeue函数是出队函数,返回类型是T表示的类型。

实现接口IQueue具体方式有很多,可以是List(列表结构)、Set(集结构)或Hash(散列结构)等多种不同方式,下面笔者给出一个基于List实现方式,代码如下:

//代码文件:chapter15/src/com/a51work6/section4/ListQueue.kt

package com.a51work6.section4

import java.util.ArrayList

class ListQueue : IQueue {

// 声明保存队列元素集合items

private val items:MutableList

// init代码块初始化是集合items

init {

this.items = ArrayList()

}

override fun queue(item: T) {

this.items.add(item)

}

override fun dequeue(): T? {

return if (items.isEmpty()) {

null

} else {

this.items.removeAt(0)

}

}

override fun toString(): String {

return items.toString()

}

}

上述实现代码与上一节Queue类很相似,只是实现了IQueue接口不同。读者需要注意的实现泛型接口的具体类也应该支持泛型,所以Queue中类型参数名要与IQueue接口中的类型参数名一致。

本章小结

本章介绍了Kotlin中的泛型技术,包括泛型概念、泛型函数、泛型属性、泛型类和泛型接口等。广大读者通过本章的学习应该使用泛型的优势。

继续阅读