前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Kotlin 泛型:基本使用

Kotlin 泛型:基本使用

原创
作者头像
Kkkiro
修改2023-02-24 22:58:45
1.6K0
修改2023-02-24 22:58:45
举报
文章被收录于专栏:爬坑之路爬坑之路

泛型在 Kotin 的日常使用中运用很广泛:

  1. 当我们使用 List、Array 等类型时,我们会使用到泛型类;
  2. 当我们使用 apply、let 等函数时,我们会使用到泛型函数。

在 Kotlin 中声明和使用泛型类、泛型函数的基本概念和 Java 相似,有 Java 泛型概念的情况下,不用详细解释或者做进一步了解,也能够很容易地上手使用泛型。

但使用泛型仅仅是第一步,要想将泛型应用好,仍然需要做进一步深入的学习。

本篇是 Kotlin 泛型的基础介绍,进阶内容可点击链接查看。

系列持续更新中,欢迎关注订阅。

为什么需要泛型

假如我们想实现自定义的列表类型,用于存放数值、字符串或其他具体的类型。我们可以这么写:

代码语言:text
复制
// 数值列表
interface NumberList {
    fun set(index: Int, obj: Number?)
    fun get(index: Int): Number?
}

// 字符串列表
interface StringList {
    fun set(index: Int, obj: String?)
    fun get(index: Int): String?
}

// Car 列表
class Car 
interface CarList {
    fun set(index: Int, obj: Car?)
    fun get(index: Int): Car?
}

如果没有泛型,我们只能针对每种具体的类型,分别定义对应的列表,这种方式只能针对有限的具体类型进行实现、不同具体类型的列表实际上具有相似的实现,这些代码只能在不同列表间拷贝重复,无法复用,难以维护。

有的同学会用这样的方法来解决上面的问题:

代码语言:txt
复制
interface AnyList {
    fun set(index: Int, obj: Any?)
    fun get(index: Int): Any?
}

这个方法虽然能解决上述问题,但它带来了其他的问题。

首先,列表中存放的数据类型信息消失了,从函数签名上,我们只知道能得到一个实例,但这个实例具体是什么类型就无从得知,作为列表的使用者,面对一个未知的接口,开发体验别提有多糟糕了。

其次,Kotlin 是静态类型语言,静态类型语言的优势是能够在编译时帮我们提前进行类型检查,保证类型的正确性,避免潜在的类型错误。而上面这个例子,由于任何类型都是 Any 类型的子类,在进行类型检查时,Kotlin 无法帮我们检查出不合理的调用,我们完全可以往一个 String 列表里放入一个 Number 实例,从而让使用者从一个 Car 列表中得到猫猫狗狗,这都是完全有可能的。这种看似灵活的万能列表,实际上是随时会爆炸的炸弹,严重降低了工程质量(请注意倒车,请注意倒车。

什么是泛型

泛型提供了一种方法,允许我们定义带「类型参数」的泛型类/泛型函数,在创建泛型类的实例、调用泛型函数时,「类型参数」将替换成具体的「类型实参」。

上面的例子用泛型定义将会很方便简洁,同时,类型信息得到了保留,编译器也能正常进行类型检查:

代码语言:text
复制
interface List<T> {
    fun set(index: Int, obj: T?)
    fun get(index: Int): T?
}

val stringList: List<String> = // ... 省略
stringList.set(0, "a string") // OK
stringList.get(0)?.charAt(0) // OK
stringList.set(0, 1) // 编译出错,类型不匹配
stringList.get(0) - 1 // 编译出错,类型不匹配

class Car
val carList: List<Car> = // ... 省略
carList.set(0, Car()) // OK
carList.get(0) is Car? // Always true
carList.set(0, 1) // 编译出错,类型不匹配
carList.get(0) is Int? // 编译出错,类型不匹配

泛型机制允许我们在编码的时候,使用占位符作为类型(即「类型参数」代替实际使用时的类型(即「类型实参」)。

如何区别上述两个概念?

当我们在「定义」泛型类、泛型函数时,我们使用的是「类型参数」;

当我们在「使用」泛型类、泛型函数时,我们使用的是「类型实参」。

「类型参数」是占位符,就像变量一样,可以任意取名,一般使用单个大写字母(T、U、V)、全大写单词(DATA、TOKEN)、或首字母大写的单词(Data、Token);

「类型实参」是具体的类型,只能传入已存在的具体类型,如 IntStringAny 或者其他自定义的具体类型。

定义泛型类、泛型函数的方式如下:

代码语言:text
复制
// --- 泛型函数 ---
fun <P> run(param: P) // 仅用于函数参数,定义在泛型类、泛型接口中
fun <R> run(): R // 仅用于函数返回值,定义在泛型类、泛型接口中
fun <P, R> invoke1(param: P): R // 用于函数参数和返回值,定义在泛型类、泛型接口中
fun <T> filter(predicate: (T) -> Boolean) // 用于高阶函数

// --- 泛型类 ---
class Box<T> { // 泛型类
    private var instance: T? // 用于属性
    // 类中的泛型函数
    fun get(): T? { // 用于方法,下同
        return instance
    }
    fun set(instance: T?) {
        this.instance = instance
    }
}
interface List<T> { // 泛型接口
    fun set(index: Int, obj: T) // 用于方法,下同
    fun get(index: Int): T?
}

使用泛型类、泛型函数:

代码语言:txt
复制
// 使用泛型函数
filter<String> { it: String -> false }

// 使用泛型类
val stringBox = Box<String>()

// 使用泛型接口
class Car
class CarList : List<Car> {
    // 实现接口中的泛型函数
    override fun set(index: Int, obj: Car) {
        // todo
    }
    override fun get(intdex: Int): Car? {
        // todo
    }
}
val carList = CarList()
carList.set(0, Car())
carList.get(0) is Car? // Always true

了解到这里,就掌握了基本的泛型使用方式:

  1. 用「类型参数」作为占位符,定义泛型类、泛型函数
  2. 使用泛型类、泛型函数时,需要传递具体类型作为「类型实参」。

下一篇文章,将介绍 Kotlin 泛型的进阶知识:类型参数约束

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 为什么需要泛型
  • 什么是泛型
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档