【编程语言】Kotlin快速入门 - 泛型
Kotlin的泛型与Java十分类似,也是使用这种的语法结构:
class Fruit<T> {fun eat(f : T): Unit {println("eat...")}fun <T> buy(p : T): T {return p}
}
泛型限制
和Java一样,Kotlin也允许泛型是某个类的子类
fun <T : Number> other(p : T): T {return p
}
泛型实化
Java的泛型是使用泛型擦除机制来实现的,所以只存在于编译时期,所以所有基于JVM的语言他们的泛型都是通过类型擦除机制实现的,Kotlin也不例外,这种机制使得我们不可能使用a is T
或者T::class.java
这些的写法了,因为T在运行时类型已经被擦除了。
然而Kotlin剔红了一个内联函数的概念,内联函数在编译时会自动被替换它被调用的地方,这样也就不存在什么泛型擦除的问题了,因为代码在编译的时候会直接使用实际的类型来替代内联函数中的泛型声明。
这意味着Kotlin是可以将内联函数中的泛型进行实化的,那怎样实现呢?首先函数必须是内联函数且声明泛型的地方需要使用reified
进行修饰,举个例子:
fun main() {var r = getGenericType<String>()var m = getGenericType<Int>()println("r is $r")println("m is $m")
}inline fun <reified T> getGenericType() = T::class.java
输出结果:r is class java.lang.String
m is class java.lang.Integer
而Java是不可能实现上述功能的,因为在编译后,泛型这个约束对于JVM已经不存在了。
泛型协变
泛型的协变与逆变功能就基本用不到,但是还是讲一下,避免在看别人代码的时候看不懂是什么意思。
首先我们需要讲一个概念,在泛型类或者泛型方法中,参数列表是接收数据的地方,因此可以称它的位置是in位置,而返回类型的地方是输出数据的地方,因此可以称它为out。
接下来我们定义三个类:
open class Person(val name: String, val age: Int)
class Student(name: String, age: Int): Person(name, age)
class Teacher(name: String, age: Int): Person(name, age)
接下来问你一个问题,假设在Java中一个方法接收Person类型的参数,而你传递一个Student可以吗,当然可以吧。
但是,如果方法接收的参数是一个List你传递一个List这样可以吗,这样是不行的,不行你去试一下,不行的原因主要是Java为了防止类型转换的安全隐患,我们举个例子,假设有个Data的泛型类:
class Data<T> {var value: T? = nullfun set(v: T?) {this.value = v}fun get(): T? {return this.value}
}
fun main() {var s = Student("XuanRan", 18)var d = Data<Student>()d.set(s)handle(d) // 此行报错println(d.get())
}fun handle(data: Data<Person>) {data.set(Teacher("Tom", 17))
}
我们假设报错的那一行能够正常编译通过,按照程序逻辑,如果我们上面提到的问题允许的话,那么经过handle处理后的Data将不再是原来的Student,所以为了杜绝这种情况,Java直接禁用掉了这种方式,换句话讲,即使Student是Person的子类,但是Data并不是Data的子类。
不过,我们一起回顾一下代码,出现这种问题的主要原因是因为我们在handle方法内部,通过data给设置了Teacher对象,如果Data在泛型T上是只读的话,那肯定没有类型转换异常了,所以我们只要确保泛型类在其泛型数据上是只读的话那么它是没有类型转换安全隐患的,换句话说我们在Data类里面,只要保留get方法,不要暴露set方法供外部调用,只在构造方法中进行参数设置即可,要实现这一点,只要让这个泛型类的所有方法(构造除外)都不能去接收T类型的参数,换句话说,T只能出现在out位置,而不是in位置,如下代码所示:
class Data<out T>(val value: T) {fun get(): T? {return this.value}
}
我们在T之前使用out进行修饰,此时类中所有含参数T的方法都会报错,并且我们在这个类中添加常量value,此时,我们再或过头看之前的代码,会发现handle调用不报错了,而handle方法的实现中,调用Data内部的set方法报错,这很正常,因为我们删掉了set。
fun main() {var s = Student("XuanRan", 18)var d = Data(s)handle(d)println(d.get())
}fun handle(data: Data<Person>) {// todo
}
这样我们就实现了泛型的协变。
除此之外,Kotlin已经给很多内置的API加上了协变的声明,其中就包括了各种集合类与接口,还记得我们之前提到的使用listOf只能创建只读的集合吗,我们现在就来看一下List的精简源码
public interface List<out E> : Collection<E> {override val size: Intoverride fun isEmpty(): Booleanoverride fun contains(element: @UnsafeVariance E): Booleanoverride fun iterator(): Iterator<E>public operator fun get(index: Int): E
}
List在泛型E的前面加上了out关键字,说明List在泛型E上是协变的。不过这里还有一点需要说 明,原则上在声明了协变之后,泛型E就只能出现在out位置上,可是你会发现,在 contains()方法中,泛型E仍然出现在了in位置上。
这么写本身是不合法的,因为在in位置上出现了泛型E就意味着会有类型转换的安全隐患。但是 contains()方法的目的非常明确,它只是为了判断当前集合中是否包含参数中传入的这个元 素,而并不会修改当前集合中的内容,因此这种操作实质上又是安全的。那么为了让编译器能够理解我们的这种操作是安全的,这里在泛型E的前面又加上了一个@UnsafeVariance注解, 这样编译器就会允许泛型E出现在in位置上了。
泛型逆变
我们定义一个Transformer对象,并接收一个Person的泛型,此外要求接口实现类必须重写自己的trans方法,然后我们在main中实现了这个接口,这个接口trans的返回值将会返回Person的名字和姓名,并且定义了一个方法handle,用于输出一个学生的姓名,但是handle在调用的时候会报错语法错误,因为方法要求接收的是Transformer的泛型,而传递过来的却是Transformer的泛型,换句话说,Transformer并不是Transformer的子类型。
interface Transformer<T> {fun trans(t: T): String
}fun main() {// 实现一个Transformer接口val t = object : Transformer<Person> {// 定义我们自己的trans方法override fun trans(t: Person): String {// 该方法会返回Person类的名称和年龄return "${t.name} ${t.age}"}}handle(t) // 此处语法错误
}/*** 该函数接收一个Transformer的对象,要求泛型为Student*/
fun handle(s: Transformer<Student>) {var student = Student("XuanRan", 12)println(s.trans(student))
}
要解决这个问题,我们就可以使用泛型的逆变,也就是in关键字,修改Transformer代码,如下所示:
interface Transformer<in T> {fun trans(t: T): String
}
然后代码就能正常编译输出了,这就是泛型的逆变。
相关文章:
【编程语言】Kotlin快速入门 - 泛型
Kotlin的泛型与Java十分类似,也是使用这种的语法结构: class Fruit<T> {fun eat(f : T): Unit {println("eat...")}fun <T> buy(p : T): T {return p} }泛型限制 和Java一样,Kotlin也允许泛型是某个类的子类 fun &l…...
【PostgreSQL】入门篇——在不同操作系统上安装 PostgreSQL
PostgreSQL在 Windows、macOS 和 Linux(以 Ubuntu 为例)的安装步骤,以及可能出现的问题和解决办法。 一、在 Windows 上安装 PostgreSQL 1. 下载 PostgreSQL 安装程序 访问 PostgreSQL 官方网站:PostgreSQL Downloads点击“Dow…...

【Docker】部署MySQL容器
关于docker,Windows上使用Powershell/CMD执行指令,Linux系统直接使用终端执行指令。 拉取MySQL 也可以跳过拉取步骤,直接run,这样本地容器不存在的话,会自动拉取最新/指定的版本。 # 默认拉取最新版本 docker pull …...

mysql9.0windows安装
第一步下载 官网地址:https://dev.mysql.com/downloads/mysql/ 点击后,选择不登录下载 第二步安装 双击下载的msi文件进行安装。打开后页面如下,选择安装类型,选择自定义安装。点击Next下一步。 自行选择安装目录 选好后点击…...

word中文献引用[]符号的上下标格式修改
word中文献引用[]符号的上下标格式修改 百度网址 1、查找打开使用通配符,输入[[][0-9]{1,2}[]],即可匹配所有的字[1],[12]这些字符,然后鼠标点击替换为的空白处,再点击特殊格式–>“字体”,选中上标,最…...

计算机毕设-基于springboot的游戏创意工坊与推广平台的设计与实现(附源码+lw+ppt+开题报告)
博主介绍:✌多个项目实战经验、多个大型网购商城开发经验、在某机构指导学员上千名、专注于本行业领域✌ 技术范围:Java实战项目、Python实战项目、微信小程序/安卓实战项目、爬虫大数据实战项目、Nodejs实战项目、PHP实战项目、.NET实战项目、Golang实战…...

kafka的备份策略:从备份到恢复
文章目录 一、全量备份二、增量备份三、全量恢复四、增量恢复 前言:Kafka的备份的单元是partition,也就是每个partition都都会有leader partiton和follow partiton。其中leader partition是用来进行和producer进行写交互,follow从leader副本进…...

【畅购商城】微信支付之支付回调和支付状态
目录 Nuxt.js IP 启动 支付回调 回调接口 后端实现 查看支付状态 后端实现 前端实现 前置技术:RabbitMQ 更新订单状态 Nuxt.js IP 启动 "config": {"nuxt": {"host": "0.0…...

【Compose multiplatform教程18】多平台资源的设置和配置
要正确配置项目以使用多平台资源,请执行以下操作: 添加库依赖项。 为每种资源创建必要的目录。 为限定资源创建其他目录(例如,深色 UI 主题或本地化字符串的不同图像)。 依赖项和目录设置 要访问多平台项目中的资源…...

MT6765核心板_MTK6765安卓核心板规格参数_联发科MTK模块开发
MTK6765安卓核心板是基于联发科高效八核处理器平台开发的一款强大硬件解决方案。这款核心板的核心是采用12纳米工艺打造的MTK6765 CPU,具备四个主频高达2.3GHz的CORTEX-A53核心和四个主频为1.8GHz的CORTEX-A53核心,提供了卓越的处理性能。用户可以根据需…...
conda常用维护命令
文章目录 1. 初始化和更新 Conda更新 Conda初始化 Conda(如果需要) 2. 管理环境创建新环境激活环境停用当前环境列出所有环境删除环境 3. 管理包安装包卸载包更新包更新所有包查找包列出已安装包 4. 导入导出环境导出环境配置从文件创建环境 5. 管理通道…...

Html——10 关键字和描述
<!DOCTYPE html> <html><head><meta charset"UTF-8"><title>淘宝网</title><meta name"keywords" content"我要自学网,自学HTML,自学CSS"/><meta name"description" content"要设置…...
Mysql(MGR)和ProxySQL搭建部署-Docker版本
项目路径:D:\study\backend\mysql\mgr 一、Mysql(MGR) 1.1 docker-compose.yaml volumes: # MySQL配置文件目录 - "./mysql-1/conf.d/my.cnf:/etc/mysql/my.cnf" # MySQL工作目录 - "./mysql-1/data:/var/lib/mysql" …...
QML学习(一) Qt Quick和QML介绍以及适用场景说明
一.介绍 1.Qt Quick 介绍 Qt Quick 提供了一套高动态,丰富的 QML 元素来定制用户界面的说明性框架。Qt Quick 有助于程序开发员与界面设计员的合作为便携式设备建立流畅的用户界面,例如:移动电话、媒体播放器,机顶盒以及上网本等…...
深入理解 PyTorch 的 view() 函数:以多头注意力机制(Multi-Head Attention)为例 (中英双语)
深入理解 PyTorch 的 view() 函数:以多头注意力机制(Multi-Head Attention)为例 在深度学习模型的实现中,view() 是 PyTorch 中一个非常常用的张量操作函数,它能够改变张量的形状(shape)而不改…...
使用PHP函数 “setcookie“ 设置cookie
在网站开发中,cookie是一种非常常用的技术,它用于在用户的浏览器中存储少量的数据,以便在不同页面之间传递信息。PHP提供了一个名为 "setcookie" 的函数,用于设置cookie的值和属性。在本文中,我们将学习如何…...
redis优化
在高并发、高性能、高可用系统中,Redis 的优化至关重要。以下是一些在面试中可以详细说明的 Redis 优化策略,以及具体的实践经验和技术亮点: 1. 数据模型与结构设计优化 使用合适的数据结构 :根据业务需求选择合适的 Redis 数据结…...
数据分析的革命——解读云数据库 SelectDB 版的力量
在当今数据驱动的时代,实时数据分析已成为企业决策中的关键一环。如何在海量数据中快速找到核心价值,如何让决策者在毫秒间洞悉变化,这不仅考验着企业的技术能力,也对基础设施提出了新的要求。云数据库 SelectDB 版,正…...

Ngnix介绍、安装、实战及用法!!!
一、Nginx简介 1、Nginx概述 Nginx (“engine x”) 是一个高性能的 HTTP 和 反向代理服务器,特点是占有内存少,并发能力强,能经受高负载的考验,有报告表明能支持高达 50,000 个并发连接数 。 2、正向代理 正向代理:如果把局…...

算法基础一:冒泡排序
一、冒泡排序 1、定义 冒泡排序(英语:Bubble Sort)是一种简单的排序算法。它重复地走访过要排序的数列,一次比较两个元素,如果他们的顺序(如从大到小、首字母从A到Z)错误就把他们交换过来。 …...

网络编程(Modbus进阶)
思维导图 Modbus RTU(先学一点理论) 概念 Modbus RTU 是工业自动化领域 最广泛应用的串行通信协议,由 Modicon 公司(现施耐德电气)于 1979 年推出。它以 高效率、强健性、易实现的特点成为工业控制系统的通信标准。 包…...
ES6从入门到精通:前言
ES6简介 ES6(ECMAScript 2015)是JavaScript语言的重大更新,引入了许多新特性,包括语法糖、新数据类型、模块化支持等,显著提升了开发效率和代码可维护性。 核心知识点概览 变量声明 let 和 const 取代 var…...

docker详细操作--未完待续
docker介绍 docker官网: Docker:加速容器应用程序开发 harbor官网:Harbor - Harbor 中文 使用docker加速器: Docker镜像极速下载服务 - 毫秒镜像 是什么 Docker 是一种开源的容器化平台,用于将应用程序及其依赖项(如库、运行时环…...

从WWDC看苹果产品发展的规律
WWDC 是苹果公司一年一度面向全球开发者的盛会,其主题演讲展现了苹果在产品设计、技术路线、用户体验和生态系统构建上的核心理念与演进脉络。我们借助 ChatGPT Deep Research 工具,对过去十年 WWDC 主题演讲内容进行了系统化分析,形成了这份…...

AI Agent与Agentic AI:原理、应用、挑战与未来展望
文章目录 一、引言二、AI Agent与Agentic AI的兴起2.1 技术契机与生态成熟2.2 Agent的定义与特征2.3 Agent的发展历程 三、AI Agent的核心技术栈解密3.1 感知模块代码示例:使用Python和OpenCV进行图像识别 3.2 认知与决策模块代码示例:使用OpenAI GPT-3进…...

Python:操作 Excel 折叠
💖亲爱的技术爱好者们,热烈欢迎来到 Kant2048 的博客!我是 Thomas Kant,很开心能在CSDN上与你们相遇~💖 本博客的精华专栏: 【自动化测试】 【测试经验】 【人工智能】 【Python】 Python 操作 Excel 系列 读取单元格数据按行写入设置行高和列宽自动调整行高和列宽水平…...

汽车生产虚拟实训中的技能提升与生产优化
在制造业蓬勃发展的大背景下,虚拟教学实训宛如一颗璀璨的新星,正发挥着不可或缺且日益凸显的关键作用,源源不断地为企业的稳健前行与创新发展注入磅礴强大的动力。就以汽车制造企业这一极具代表性的行业主体为例,汽车生产线上各类…...

dedecms 织梦自定义表单留言增加ajax验证码功能
增加ajax功能模块,用户不点击提交按钮,只要输入框失去焦点,就会提前提示验证码是否正确。 一,模板上增加验证码 <input name"vdcode"id"vdcode" placeholder"请输入验证码" type"text&quo…...
什么是EULA和DPA
文章目录 EULA(End User License Agreement)DPA(Data Protection Agreement)一、定义与背景二、核心内容三、法律效力与责任四、实际应用与意义 EULA(End User License Agreement) 定义: EULA即…...

Redis数据倾斜问题解决
Redis 数据倾斜问题解析与解决方案 什么是 Redis 数据倾斜 Redis 数据倾斜指的是在 Redis 集群中,部分节点存储的数据量或访问量远高于其他节点,导致这些节点负载过高,影响整体性能。 数据倾斜的主要表现 部分节点内存使用率远高于其他节…...