当前位置: 首页 > news >正文

Kotlin 泛型

文章目录

  • 定义
    • 泛型属性
    • 泛型函数
    • 泛型类或接口
  • where 声明多个约束
  • 泛型具体化
  • in、out 限制泛型输入输出

定义

有时候我们会有这样的需求:一个类可以操作某一类型的对象,并且限定只有该类型的参数才能执行相关的操作。
如果我们直接指定该类型Int,确实可以实现该操作,但是换一个类型就得再重新定义一个类:

class MyArray {fun add(int: Int) {}fun delete(int: Int) {}
}fun main() {val myArray = MyArray()myArray.add(1)myArray.delete(1)
}

如果我们将类型改为Any,又会导致可操作参数太过笼统,还容易出现各种问题:

class MyArray {fun add(any: Any) {}fun delete(any: Any) {}
}fun main() {val myArray = MyArray()myArray.add(1)myArray.delete("abc")
}

这时候我们需要引入泛型。泛型的定义方式如下:

<泛型名[: 泛型所属类型], ...>

一般情况下,泛型名会是单个的大写字母,很多时候是T,而泛型所属类型可以指定泛型是某个类/接口或者其子类/实现(当然,单独这样写做不了什么,请继续往下看):

<T>
<T: Runnable>
<K, V>

泛型属性

在 Kotlin 中,可以为一个类定义扩展属性:

class MyClassval MyClass.name get() = "MyClass"

如果我们要指定是任何类都有的属性,则可以泛型。
泛型属性只能用在扩展,声明时需要将泛型定义放置在val|var后,变量名前。

val <T> T.name get() = "Any"fun main() {val int = 1print(int.name)
}
Any

也可以限制该泛型是Runnable接口或其实现。

val <T: Runnable> T.name get() = "Runnable"fun main() {val int = 1// print(int.name) 不可访问print(Runnable {  }.name)
}
Runnable

泛型函数

我们也可以为函数泛型,与泛型扩展属性类似,泛型定义需要写在fun和函数名之间(意外发现此时as如果无法转换不会抛出异常,而是返回any):

fun <T> cast(any: Any) = any as Tfun main() {cast<List<Double>>(listOf(1, 2)).forEach { print(it); print(", ") }
}
1.0, 2.0, 

Note:泛型可以作为函数接收者、参数、返回值类型的一部分。如果一个泛型函数在调用时可以通过传入的参数类型或返回值声明确定泛型的类型,则调用时泛型的类型可以缺省,否则需要指明其类型函数名<泛型类型, ...>()(泛型类与此类似)。

声明两个泛型(涉及到的 Kotlin 一些便捷的习语我们将在后续文章学习):

fun <K, V> hashMap(vararg pairs: Pair<K, V>) =HashMap<K, V>().apply { putAll(pairs) }fun main() {print(hashMap("key" to 1))
}
{key=1}

泛型扩展函数:

// List 类是一个泛型类
// 可以将它的泛型指定为函数的泛型
fun <T> List<T>.print() = print(this)fun main() {listOf(1, 2, 3).print()
}
[1, 2, 3]
fun <T: Comparable<Int>> T.isGreaterZero() = this > 0fun <T: Comparable<Int>> T.isGreaterZero() = this > 0fun main() {val int = 1// Int 实现了 Comparable<Int>// 因此可以调用print(int.isGreaterZero())
}
true

作为返回值:

fun <T> Any.toType() = this as Tfun main() {// 没有指定变量类型,无法推断泛型的类型// 因此调用时需要声明泛型类型val doubleList = listOf<String>().toType<List<Double>>()val intList: List<Double> = listOf<String>().toType()
}

泛型类或接口

在定义泛型类或接口时,需要将泛型定义放置在类名之后,括号之前。
泛型函数与接口是类似的,这里以类为例。

class Group<T>(vararg elements: T) {private val value = mutableListOf(*elements)fun add(element: T) = value.add(element)fun remove(element: T) = value.remove(element)// 重写 toString,调用 print 时即可打印出列表override fun toString(): String = value.toString()
}fun main() {val group = Group(1, 2, 3) // Group<Int>(1, 2, 3)println(group)group.remove(2)println(group)group.remove(4)print(group)
}
[1, 2, 3]
[1, 3]
[1, 3]

Note:vararg用于定义数量不定的参数,vararg element: Int表示你可以传入任意数量(0…无限大)的Int类型,在内部访问element会获取到一个数组Array<Int>,而使用*可以将其展开为vararg(也就是原始传入的任意数量的Int),可以用于传入另一个需要vararg的函数。

使用*可以指代所有泛型(下方例子中相当于Any?*无法用在函数调用上):

fun List<*>.print() = print(this)fun main() {listOf(1, 2, 3).print()println()listOf("a", "b", "c").print()
}
[1, 2, 3]
[a, b, c]

where 声明多个约束

我们可以使用<T: 类型>来约束泛型为某一类型或其子类。但有时候我们想要将其约束于多个类型,可以使用where来实现(此时<T: 类型>中的类型也应该一并移到where处):
我们先定义一个接口和一个类,后面的例子会反复用到:

interface MyInterfaceclass MyClass : Runnable, MyInterface {override fun run() {}
}
  1. 泛型扩展属性

    // 只有实现了 Runnable 和 MyInterface 才能拥有此扩展属性
    val <T> T.id where T: Runnable, T: MyInterfaceget() = 123fun main() {MyClass().id
    }
    
  2. 泛型函数

    // 只有实现了 Runnable 和 MyInterface 才能拥有此扩展函数
    fun <T> T.getId() where T : Runnable, T : MyInterface = 123// 只有泛型 T 实现了 Runnable 和 MyInterface 才能使用此函数
    fun <T> getName() where T : Runnable, T : MyInterface ="Runnable&MyInterface"fun main() {MyClass().getId()getName<MyClass>()
    }
    
  3. 泛型类或接口

    // 只有实现了 Runnable 和 MyInterface 才能作为构造参数 value 传入
    class MyContainer<T>(val value: T) where T : Runnable, T : MyInterfacefun main() {MyContainer(MyClass())
    }
    

泛型具体化

有时候我们需要判断泛型的类型,会想到用类型::class获取对应的KClass进行对比。而当该类型为泛型时,我们需要将其具象化reified,通常该声明只能用于函数,并且需要与inline搭配使用:

inline fun <reified T> T.isInt() = T::class == Int::classfun main() {println(1.isInt())print(false.isInt())
}
true
false

in、out 限制泛型输入输出

inout修饰泛型只能用在类或接口。
当一个泛型被in修饰时,表明该类型只能作为对象属性、方法的输入(赋值、传参):

class Group<in T>(vararg elements: T) {// 因为泛型声明为 in,属性类型中包含 T 可见性只能为 privateprivate val value = mutableListOf(*elements)fun add(element: T) {}// 因为泛型声明为 in,不能定义返回 T 类型的方法// fun get(index: Int) = value[index]
}

当一个泛型被in修饰时,表明该类型只能作为对象属性、方法的输出(获取值、函数返回):

class Group<out T>(vararg elements: T) {// 因为泛型声明为 out,而 MutableList<T> 具有 in 和 out// 若将其公开,将会允许 in T,例如 value.add(element: T)// 属性类型中包含 T 可见性只能为 privateprivate val value = mutableListOf(*elements)// 因为泛型声明为 out,不能作为参数类型// fun add(element: T) {}fun get(index: Int) = value.get(index)
}

Note:注意是针对对象而言,如果是一个类声明了out泛型,仍然可以将该泛型作为构造函数中的参数类型。

class Group<out T>(vararg elements: T)

相关文章:

  • 处理一对多的映射关系
  • HCIP的学习(27)
  • 基于SpringBoot+Vue的公园管理系统的详细设计和实现(源码+lw+部署文档+讲解等)
  • Spring高手之路19——Spring AOP注解指南
  • 【云原生】Docker Compose 使用详解
  • Python 关于字符串格式化
  • C++ : 模板初阶
  • MFC实现守护进程,包括开机自启动、进程单例、进程查询、进程等待、重启进程、关闭进程
  • Apache Calcite - 自定义标量函数
  • Anaconda创建python环境默认C盘,如何修改路径
  • C语言PTA练习题(期末考试成绩排名,新生舞会,约瑟夫游戏(序号+姓名+密码),排队点名)
  • 【学习Day4】计算机基础
  • 网安速成之选择题(详细解析版)
  • Kmeans聚类模型
  • Polar Web【简单】login
  • [rust! #004] [译] Rust 的内置 Traits, 使用场景, 方式, 和原因
  • [译]Python中的类属性与实例属性的区别
  • Android交互
  • Angular 响应式表单之下拉框
  • CentOS学习笔记 - 12. Nginx搭建Centos7.5远程repo
  • codis proxy处理流程
  • Facebook AccountKit 接入的坑点
  • JAVA SE 6 GC调优笔记
  • redis学习笔记(三):列表、集合、有序集合
  • sessionStorage和localStorage
  • Vue组件定义
  • zookeeper系列(七)实战分布式命名服务
  • 程序员该如何有效的找工作?
  • 大整数乘法-表格法
  • 基于 Ueditor 的现代化编辑器 Neditor 1.5.4 发布
  • 解决jsp引用其他项目时出现的 cannot be resolved to a type错误
  • 开发了一款写作软件(OSX,Windows),附带Electron开发指南
  • 深度解析利用ES6进行Promise封装总结
  • 什么软件可以剪辑音乐?
  • 使用 Docker 部署 Spring Boot项目
  • 学习笔记:对象,原型和继承(1)
  • PostgreSQL 快速给指定表每个字段创建索引 - 1
  • ​iOS安全加固方法及实现
  • ​ssh免密码登录设置及问题总结
  • #NOIP 2014# day.1 生活大爆炸版 石头剪刀布
  • #Ubuntu(修改root信息)
  • #我与虚拟机的故事#连载20:周志明虚拟机第 3 版:到底值不值得买?
  • (ISPRS,2023)深度语义-视觉对齐用于zero-shot遥感图像场景分类
  • (Matalb分类预测)GA-BP遗传算法优化BP神经网络的多维分类预测
  • (Redis使用系列) Springboot 实现Redis 同数据源动态切换db 八
  • (二)springcloud实战之config配置中心
  • (六)什么是Vite——热更新时vite、webpack做了什么
  • (原)Matlab的svmtrain和svmclassify
  • .Net高阶异常处理第二篇~~ dump进阶之MiniDumpWriter
  • .NET教程 - 字符串 编码 正则表达式(String Encoding Regular Express)
  • .NET中使用Redis (二)
  • .sh
  • [2016.7 Day.4] T1 游戏 [正解:二分图 偏解:奇葩贪心+模拟?(不知如何称呼不过居然比std还快)]
  • [2024-06]-[大模型]-[Ollama] 0-相关命令
  • [Android View] 可绘制形状 (Shape Xml)