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

Kotlin 泛型小知识: `<T>`, `<out T>`, `<in T>` 的区别

在这里插入图片描述

引言

我们是不是常常在 Kotlin 的代码中看到一些奇怪的符号,比如 <out T> 或者 <T>?如果我们对这些泛型(Generics)符号还不太了解,没关系!今天我们就来聊一聊它们的区别,以及如何在实际开发中正确使用它们。😊

泛型:它是什么?为什么要用?📚

首先,让我们简单了解一下**泛型(Generics)**是什么。泛型就是一种设计模式,它让我们的代码更加通用化,能够适用于多种数据类型,而不是被限制在某一种类型上。

举个例子:

fun printItem(item: Any) {println(item)
}

这个 printItem 函数看起来不错,它接受 Any 类型的参数,意味着任何类型的对象都能传进来。😃

但是,Kotlin 的泛型可以让我们做得更好!假设我们希望创建一个可以存储和获取不同类型数据的容器类,我们可以使用泛型来定义它:

class Container<T>(var item: T) {fun getItem(): T {return item}
}

这里的 <T> 就是泛型标记,表示我们将使用一个类型 T,并且这个类型是由使用者来指定的。

那么,<T><out T> 有什么区别呢?🧐

1. <T>:协变与逆变

在 Kotlin 中,泛型类型参数是不变的(Invariant)。这意味着对于一个类 Container<T>,如果 AB 的子类型(A : B),那么 Container<A> 并不是 Container<B> 的子类型。比如:

val stringList: List<String> = listOf("Hello")
val anyList: List<Any> = stringList // 错误:类型不匹配

即使 StringAny 的子类型,List<String> 也不是 List<Any> 的子类型。这种类型的不兼容性在一些情况下会带来麻烦。

2. out:协变(Covariance)

为了让泛型类型在子类型关系中表现得更灵活,Kotlin 引入了协变和逆变的概念。

  • 协变:用 out 关键字表示,意味着泛型类型可以从一种类型安全地转换为另一种类型。
  • 协变适用于只读的情况,也就是说,我们只从中获取数据,不会修改其中的数据。

举个例子:

interface Source<out T> {fun nextT(): T
}

这里的 out T 表示 Source 是协变的,这意味着如果 CatAnimal 的子类型,那么 Source<Cat> 也是 Source<Animal> 的子类型。

为什么这样?因为 Source 只提供 T 类型的数据,而不修改它。这保证了类型安全。

使用 out 的场景

当我们在设计类或接口时,如果我们希望该类的泛型类型参数仅用于返回(输出)数据,而不会用于接收(输入)数据时,我们应该使用 out 关键字。🌟

fun demo(source: Source<Animal>) {val catSource: Source<Cat> = object : Source<Cat> {override fun nextT(): Cat {return Cat()}}val animalSource: Source<Animal> = catSource // 协变,安全转换
}

3. in:逆变(Contravariance)

out 相反,in 用于表示逆变(Contravariance)。in 关键字表示泛型类型参数只能用于接收(输入)数据,而不能用于返回(输出)数据。💡

逆变的应用

假设我们有一个消费者类,它只消耗数据而不产生数据:

interface Consumer<in T> {fun consume(item: T)
}

在这种情况下,如果 AnimalCat 的父类型,那么 Consumer<Animal>Consumer<Cat> 的子类型。逆变可以安全地传递更广泛类型的对象。

4. 不使用 inout:不变(Invariant)

当我们既需要输入又需要输出数据时,就不应该使用 inout。这样的泛型类型参数称为不变(Invariant)。

class Container<T>(var item: T) {fun getItem(): T = itemfun setItem(item: T) {this.item = item}
}

这里的 Container<T> 同时用于输入和输出 T 类型的数据,因此不能使用 inout,它是不变的。

总结 📝

  • <T>:默认情况下是不变的,即 Container<A> 不是 Container<B> 的子类型。
  • <out T>:表示泛型参数是协变的,只能作为返回值(输出)使用。这种情况适用于只读取数据的场景。
  • <in T>:表示泛型参数是逆变的,只能作为参数(输入)使用。这种情况适用于只传递数据的场景。

感谢阅读!

相关文章:

  • 北京网站建设多少钱?
  • 辽宁网页制作哪家好_网站建设
  • 高端品牌网站建设_汉中网站制作
  • Oracle查询优化--分区表建立/普通表转分区表
  • C++:string类(1)
  • 根DNS服务器
  • 【ROS2】PID控制
  • 2024上海初中生古诗文大会备考:单选题真题和每道题独家解析
  • 让一个元素靠右对齐
  • 如何使用pytest的fixtures以及pytest-dependency插件来管理接口之间的依赖关系(上)
  • 8.26 T4 日记和编辑器(fhq维护kmp——kmp本身含有的单射与可合并性)
  • Multi-UAV|多无人机、多场景路径规划MATLAB
  • Mac环境下Python3虚拟环境创建、Flask安装以及创建运行第一个最小的Flask项目
  • 科技改变搜索习惯:Anytxt Searcher,重新定义你的信息获取方式!
  • 学院个人信息|基于SprinBoot+vue的学院个人信息管理系统(源码+数据库+文档)
  • Linux下递归设置目标目录及其子目录和文件的权限
  • Linux的常见指令
  • [Python知识点]list列表append()和extend()的区别
  • 《Javascript数据结构和算法》笔记-「字典和散列表」
  • 【从零开始安装kubernetes-1.7.3】2.flannel、docker以及Harbor的配置以及作用
  • Android 架构优化~MVP 架构改造
  • CentOS7简单部署NFS
  • css选择器
  • es6--symbol
  • golang中接口赋值与方法集
  • Java IO学习笔记一
  • PHP 的 SAPI 是个什么东西
  • Spring Cloud Alibaba迁移指南(一):一行代码从 Hystrix 迁移到 Sentinel
  • TCP拥塞控制
  • use Google search engine
  • vue 配置sass、scss全局变量
  • 对话 CTO〡听神策数据 CTO 曹犟描绘数据分析行业的无限可能
  • 给新手的新浪微博 SDK 集成教程【一】
  • 回流、重绘及其优化
  • 将回调地狱按在地上摩擦的Promise
  • 开发基于以太坊智能合约的DApp
  • 坑!为什么View.startAnimation不起作用?
  • 聊聊redis的数据结构的应用
  • 盘点那些不知名却常用的 Git 操作
  • 如何抓住下一波零售风口?看RPA玩转零售自动化
  • 深入体验bash on windows,在windows上搭建原生的linux开发环境,酷!
  • 手机端车牌号码键盘的vue组件
  • 为物联网而生:高性能时间序列数据库HiTSDB商业化首发!
  • 异步
  • 最近的计划
  • 做一名精致的JavaScripter 01:JavaScript简介
  • 400多位云计算专家和开发者,加入了同一个组织 ...
  • Salesforce和SAP Netweaver里数据库表的元数据设计
  • 曜石科技宣布获得千万级天使轮投资,全方面布局电竞产业链 ...
  • 支付宝花15年解决的这个问题,顶得上做出十个支付宝 ...
  • #100天计划# 2013年9月29日
  • #define用法
  • #微信小程序:微信小程序常见的配置传旨
  • $ is not function   和JQUERY 命名 冲突的解说 Jquer问题 (
  • (03)光刻——半导体电路的绘制
  • (附源码)基于SpringBoot和Vue的厨到家服务平台的设计与实现 毕业设计 063133
  • (六)库存超卖案例实战——使用mysql分布式锁解决“超卖”问题
  • (免费领源码)python#django#mysql公交线路查询系统85021- 计算机毕业设计项目选题推荐