《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会用实际的类型替代。
在这里插入图片描述
调用泛型函数代码如下:
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中还可以声明泛型属性,但是这种属性一定是扩展属性,不是能是普通属性。
在这里插入图片描述
示例代码如下:
//代码文件: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)规则。
在这里插入图片描述
本节通过自定义队列集合介绍任何实现泛型类。具体实现代码如下:
//代码文件: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() ③
    }

    /**

    • 入队函数
    • @param item 参数需要入队的元素
      */
      fun queue(item: T) { ④
      this.items.add(item)
      }

    /**

    • 出队函数
    • @return 返回出队元素
      */
      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 { ①

    /**

    • 入队函数
    • @param item 参数需要入队的元素
      */
      fun queue(item: T) ②

    /**

    • 出队函数
    • @return 返回出队元素
      */
      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()
    }

    /**

    • 入队函数
    • @param item
    • 参数需要入队的元素
      */
      override fun queue(item: T) {
      this.items.add(item)
      }

    /**

    • 出队函数
    • @return 返回出队元素
      */
      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中的泛型技术,包括泛型概念、泛型函数、泛型属性、泛型类和泛型接口等。广大读者通过本章的学习应该使用泛型的优势。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值