Kotlin基础——泛型

泛型类型参数

编译器一般可以推导出类型实参

在这里插入图片描述

若创建空的list,则需要显示指定类型实参,可以用如下两种方式

val name: MutableList<String> = mutableListOf()

val name2 = mutableListOf<String>()

泛型函数

public fun <T> List<T>.slice(indices: IntRange): List<T> {
    if (indices.isEmpty()) return listOf()
    return this.subList(indices.start, indices.endInclusive + 1).toList()
}

如对于上面List的slice函数,在使用可不指定类型实参

val letters = ('a'..'z').toList()
println(letters.slice(0..2))
println(letters.slice<Char>(0..2))

泛型类

interface MyList<T> {
    operator fun get(index: Int): T
}

类型参数约束

如下指定了T为Number的子类,只能传递数字

fun <T : Number> onHalf(value: T): Double {
    return value.toDouble() / 2.0
}
println(onHalf(3))

如果需要多个约束,则使用where

fun <T> addSuffix(seq: T) where T : CharSequence, T : Appendable {
    if (!seq.endsWith(".")) {
        seq.append('.')
    }
}

val s = StringBuilder("Hello")
addSuffix(s)
println(s)

非空类型形参

默认类型形参将使用Any?作为默认上界,即可以传递null作为参数

class Processor<T> {
    fun process(value: T) {
        value?.hashCode()
    }
}

val p = Processor<String?>()
p.process(null)

若要保证类型形参不为空,需要显式使用Any为上界

class Processor<T : Any> {
    fun process(value: T) {
        value.hashCode()
    }
}

运行时的泛型

类型检查和转换

可以用is判断参数是否为List,但无法判断其类型参数,因为会泛型擦除,所以可以省略<>,或者使用<*>

fun printSum(c: Collection<Int>) {
    if (c is List<Int>) {
        println(c.sum())
    }
}
printSum(listOf(1, 2, 3))

fun printSum2(c: Collection<Int>) {
    if (c is List<*>) {
        println(c.sum())
    }
}

fun printSum3(c: Collection<Int>) {
    if (c is List) {
        println(c.sum())
    }
}

而在类型转换时,不能转换特定的类型参数,如下set<Int>转换为List<Int>会类型不匹配,而List<String>转换为List<Int>,实际是擦除后的List,但会在sum()调用时报错

fun printSum4(c: Collection<*>) {
    val intList = c as? List<Int> ?: throw IllegalStateException("list is expected")
    println(intList.sum())
}

//printSum4(setOf(1, 2, 3))     //IllegalStateException:list is expected
//printSum4(listOf("1", "2", "3")) //java.lang.ClassCastException

声明带实化类型参数的函数(类型参数不被擦除)

如下,正常情况下不能判断类型参数的类型

在这里插入图片描述

但在内联函数中的类型参数可以被实化,使用时需要用reified标记类型参数

inline fun <reified T> isA(value: Any) = value is T

println(isA<String>("abc"))
println(isA<Int>(123))

库函数filterIsInstance可用于过滤指定类型参数的元素

val items = listOf("one", 2, "three")
println(items.filterIsInstance<String>())

Kotlin的inline函数可以被Java调用但不能被内联,但带reified的inline函数不能被Java调用(因为其必须内联)

使用实化类型参数代替类引用

如下,在使用需要参数为Class的方法时

var load = ServiceLoader.load(Service::class.java)	//等价于Java的Service.class

可以通过实化类型参数来重写

 inline fun <reified T> loadService(): ServiceLoader<T>? {
     return ServiceLoader.load(T::class.java)
 }

var load = loadService<Service>()

实化类型参数的限制

优点:

  • 可以用于类型检查和转换(is、!is、as、as?)
  • 使用反射API(::class)
  • 获取Class (::class.java)
  • 作为调用其他函数的类型实参

缺点:

  • 不能创建类型参数的类的实例
  • 不能调用类型参数类的伴生对象的方法
  • 调用带实化参数类型函数的时候不能使用非实化参数类型形参作为类型实参
  • 不能把类、属性或者非内联函数的类型参数标记为refied

变型:泛型和子类型化

类、类型和子类型

对于非泛型类,类的名词可以直接当作类型使用,每一个Kotlin类可以构造两种类型,且不可空类型为可空类型的子类型

val s1: String? = null
val s2: String = "A"
fun accept(s: String?) {
}
accept(s1)
accept(s2)

而对于泛型类,需要类型实参替换泛型类的类型形参,每一个泛型类可以构造无数的类型,且它们没有任何关系

val l1: MutableList<Any> = mutableListOf()
val l2: MutableList<Int> = mutableListOf()
fun accept(l: MutableList<Any>) {
}
accept(l1)
//accept(l2)

协变:保留子类型化关系

对于类型参数T,如果用作函数参数在in位置,如果用作返回值在out位置

在这里插入图片描述

在类型参数加上out表示协变,可以保留类型参数的子类关系

public interface List<out E> : Collection<E> {
	......
}

对于不可变的List,其没有set方法(即不会出现往List<String>存入Int的情况),意味着不会对元素进行改变

val l3: List<Any> = listOf()
val l4: List<Int> = listOf()
fun accept(l: List<Any>) {
}
accept(l3)
accept(l4)
  • 构造函数的参数既不在in位置,也不在out位置,即使声明为out也仍可使用
  • 构造函数参数使用var,会生成setter,不能使用out标记
  • out/in只适用于类外部可见(public、protect和internal)的Api

逆变:反转子类型化关系

对于String的排序方法,接收一个Comparator<String>()

val s = listOf("AB", "C", "DEF")
s.sortedWith(kotlin.Comparator<String>() { s1, s2 ->
    s1.length - s2.length
})
println(s)

但它也可以接受一个更普遍的比较器Comparator<Any>()

val s = listOf("AB", "C", "DEF")
s.sortedWith(kotlin.Comparator<Any>() { s1, s2 ->
    s1.hashCode() - s2.hashCode()
})
println(s)

因为Comparator的类型参数由in修饰,是逆变的

interface Comparator<in T> {
    fun compare(e1: T, e2: T): Int {
       
    }
}

同时存在协变和逆变

对于同时存在in和out的函数,可用(P) -> R表示

interface Funtion1<in P, out R> {
    fun invoke(p: P): R
}

如下将Animal作为Cat逆变传参,将返回值Int作为Number协变返回

open class Animal {
    open fun getIndex(): Int {
        return 0
    }
}
class Cat : Animal() {
    override fun getIndex(): Int {
        return 1
    }
}
fun getCatIndex(f: (Cat) -> Number) {
}
getCatIndex(Animal::getIndex)

使用点变型:在类型出现的地方指定变型

上面在定义类和接口使用in和out叫做声明点变型,而在调用函数传递参数时使用in和out叫做使用点变型,如在Java中的List<T>,在写函数时可用使用通配符限制 (? exends 和 ? super)

fun <T> MutableList<T>.copy(source: MutableList<T>, destination: MutableList<T>) {
    for (item in source) {
        destination.add(item)
    }
}

val intItem = mutableListOf(1, 2, 3)
val anyItem = mutableListOf<Any>()
//copy(intItem, anyItem)

对于上面的copy函数,source只读,destination只写,但实际不能把MutableList<String>拷贝到MutableList<Any>,要实现这个功能,需要引入第二个类型参数,修改为

fun <T : R, R> copy(source: MutableList<T>, destination: MutableList<R>) {
    for (item in source) {
        destination.add(item)
    }
}

val intItem = mutableListOf(1, 2, 3)
val anyItem = mutableListOf<Any>()
copy(intItem, anyItem)

更为简单的方式是使用out,让其在使用点变型,对于只读的source可使用子对象

fun <T> copy(source: MutableList<out T>, destination: MutableList<T>) {
    for (item in source) {
        destination.add(item)
    }
}
val intItem = mutableListOf(1, 2, 3)
val anyItem = mutableListOf<Any>()
copy(intItem, anyItem)

使用*代替类型参数

  • MutableList<*>包含某种特定类型元素的列表,但不知道是哪个类型
  • MutableList<Any?>包含任意类型的元素的列表

不能创建MutableList<*>且不可写,但可读,因为所有类型可转为Any?

当类型实参的信息并不重要时,使用星号投影

fun printFirst(list: List<*>) {
    if (list.isNotEmpty()) {
        println(list.first())
    }
}

上面代码也可以通过引入类型参数替换

fun <T> printFirst(list: List<T>) {
    if (list.isNotEmpty()) {
        println(list.first())
    }
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mfbz.cn/a/394673.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

宝塔安装MySQL、设置MySQL密码、设置navicat连接

1、登录宝塔面板进行安装 2、设置MySQL连接密码 3、安装好了设置navicat连接 登录MySQL [roothecs-394544 ~]# mysql -uroot -p Enter password: 切换到MySQL数据 mysql> use mysql Database changed mysql> 查询用户信息 mysql> select host,user from user; ---…

生信学院|02月23日《ECAD数据到MCAD模型》

课程主题&#xff1a;ECAD数据到MCAD模型 课程时间&#xff1a;2024年02月23日 14:00-14:30 主讲人&#xff1a;陈冬冬 生信科技 售后服务工程师 CircuitWorks概述CircuitWorks工具栏&#xff1b;零部件库和属性信息&#xff1b;对ECAD数据的基本操作&#xff1b;将装配体输…

绝地求生:pubg全年活动整理

2023年整理&#xff0c;2024展望。2023年1月是4神兽&#xff0c;24年2月是西游&#xff0c;25年1月呢&#xff1f; 2023新3月4神兽结束后是AUG黑箱和6周年。 2024年3月也会出成长型武器黑箱和7周年。 4月&#xff1a;新通行证、战队联名、服装套装。 5月&#xff1a;是一些套装…

学习Android的第十六天

目录 Android 自定义 Adapter Adapter 接口 SpinnerAdapter ListAdapter BaseAdapter 自定义 BaseAdapter 参考文档 Android ListView 列表控件 ListView 的属性和方法 表头表尾分割线的设置 列表从底部开始显示 android:stackFromBottom 设置点击颜色 cacheColorH…

基于Java SSM框架实现精准扶贫管理系统项目【项目源码】计算机毕业设计

基于java的SSM框架实现精准扶贫管理系统演示 JSP技术介绍 JSP技术本身是一种脚本语言&#xff0c;但它的功能是十分强大的&#xff0c;因为它可以使用所有的JAVA类。当它与JavaBeans 类进行结合时&#xff0c;它可以使显示逻辑和内容分开&#xff0c;这就极大的方便了用户的需…

【HarmonyOS】鸿蒙开发之Slider组件——第3.5章

组件应用场景: 设备音量大小&#xff0c;调节屏幕亮度等需求 slider组件内options属性简介 value&#xff1a;滑动条当前进度值。 min&#xff1a;设置滑动条设置最小值。 max&#xff1a;设置滑动条设置最大值&#xff0c;默认为 100 。 step&#xff1a;设置滑动条滑动跳动…

遨博I20协作臂关节逆解组Matlab可视化

AUBO I20协作臂关节逆解组Matlab可视化 前言1、RTB使用注意点2、代码与效果2.1、完整代码2.2、运行效果 总结 前言 注意&#xff1a;请预先配置好Matlab和RTB机器人工具箱环境&#xff0c;本文使用matlab2022b和RTB10.04版本 工作需要&#xff0c;使用matlab实现对六轴机械臂…

初识 Rust 语言

目录 前言一、Rust 的背景二、Rust的特性三、部署开发环境&#xff0c;编写一个简单demo1、在ubuntu 20.04部署环境2、编写demo测试 四、如何看待Linux内核引入Rust 前言 自Linux 6.1起&#xff0c;初始的Rust基础设施被添加到Linux内核中。此后为了使内核驱动程序能够用Rust编…

Vue3+vite搭建基础架构(6)--- 使用vue-router

Vue3vite搭建基础架构&#xff08;6&#xff09;--- 使用vue-router 说明官方文档安装vue-router使用vue-router测试vue-router 说明 这里记录下自己在Vue3vite的项目使用vue-router的过程&#xff0c;不使用ts语法&#xff0c;方便以后直接使用。这里承接自己的博客Vue3vite搭…

在SpringBoot中@PathVariable与@RequestParam的区别

PathVariable GetMapping("/{userId}")public R<User> getUserById(PathVariable Long userId) {return userService.getUserById(userId);} // 根据id获取一条数据 function getStudentDataByIdAndDisplayInput(id) {// 发送 AJAX 请求$.ajax({url: /dorm/st…

基于JavaWeb开发的小区车辆登记系统计算机毕设[附源码]

基于JavaWeb开发的小区车辆登记系统计算机毕设[附源码] &#x1f345; 作者主页 央顺技术团队 &#x1f345; 欢迎点赞 &#x1f44d; 收藏 ⭐留言 &#x1f4dd; &#x1f345; 文末获取源码联系方式 &#x1f4dd; &#x1f345; 查看下方微信号获取联系方式 承接各种定制系统…

Linux环境安装Maven(详细图文)

目录 摘要 一、准备工作 1.检查当前环境是否安装maven 2.下载maven ​3.上传maven压缩包 4.解压maven包 5.移动到/usr/local目录下方便管理 6.配置maven环境变量 7.刷新配置文件 8.配置maven镜像仓库 9.验证是否成功 摘要 笔者Linux环境为&#xff1a;Ubuntu 22.04 …

安装部署k8s集群

系统&#xff1a; CentOS Linux release 7.9.2009 (Core) 准备3台主机 192.168.44.148k8s-master92.168.44.154k8s-worker01192.168.44.155k8s-worker02 3台主机准备工作 关闭防火墙和selinux systemctl disable firewalld --nowsetenforce 0sed -i s/SELINUXenforcing/SELI…

MySQL进阶45讲【22】MySQL是怎么保证数据不丢的?

1 前言 今天这篇文章&#xff0c;继续介绍在业务高峰期临时提升性能的方法。从文章标题“MySQL是怎么保证数据不丢的&#xff1f;”&#xff0c;就可以看出来&#xff0c;今天介绍的方法&#xff0c;跟数据的可靠性有关。 在专栏前面文章中&#xff0c;着重介绍了WAL机制&…

软考学习--计算机组成原理与体系结构

计算机组成原理与体系结构 数据的表示 进制转换 R 进制转换为 10 进制–按权展开法 10进制转换为2进制 原码 反码 补码 移码 原码 &#xff1a;数字的二进制表示反码 &#xff1a; 正数的反码等于原码&#xff0c;负数的反码等于原码取反补码&#xff1a; 正数的补码等…

python使用工厂模式和策略模式实现读文件、分析内容功能

当涉及到在 Python 中创建类以及使用设计模式来实现读取文件和分析内容的功能时&#xff0c;我们可以考虑使用工厂模式和策略模式的结合。下面是一个简单的示例&#xff0c;演示如何通过创建类和使用设计模式来实现这一功能&#xff1a; # 工厂模式&#xff1a;根据不同的分析…

qwen1.5 chat vllm推理使用案例

参考:https://github.com/QwenLM/Qwen1.5 下载:https://huggingface.co/collections/Qwen/qwen15-65c0a2f577b1ecb76d786524 下载可以参考huggingface-cli 命令下载使用:https://blog.csdn.net/weixin_42357472/article/details/1326636931、vllm运行 显卡驱动:NVIDIA-S…

java数据结构与算法刷题-----LeetCode239. 滑动窗口最大值

java数据结构与算法刷题目录&#xff08;剑指Offer、LeetCode、ACM&#xff09;-----主目录-----持续更新(进不去说明我没写完)&#xff1a;https://blog.csdn.net/grd_java/article/details/123063846 1. 法一&#xff1a;指针法 解题思路 我们以每一个窗口来看&#xff0c;找…

Linux+Win双系统远程重启到Win

背景 电脑安装了双系统&#xff08;ubuntu 22.04 win11&#xff09;&#xff0c;默认进入ubuntu系统。给电脑设置了WoL(Wake-on-LAN)&#xff0c;方便远程开机远程控制。 但是ubuntu的引导程序grub无法远程控制&#xff0c;远程开机会默认进入ubuntu。 虽然说可以进入ubuntu后…

【STM32】硬件SPI读写W25Q64芯片

目录 基础知识回顾&#xff1a; SPI外设简介 SPI框图 主模式全双工连续传输 非连续传输 初始化SPI外设 核心代码 - 交换一个字节 硬件接线图 Code 程序配置过程 MySPI.c MySPI.h W25Q64.c W25Q64.h W25Q64_Ins.h main.c 基础知识回顾&#xff1a; 【STM32】SP…
最新文章