Kotlin开发笔记:集合和逆变协变

news2025/1/13 13:53:25

Kotlin开发笔记:集合和逆变协变

在这里插入图片描述

Kotlin中的集合

基本的集合类型

Kotlin中的集合类型和Java差不多,不过有些在名称上可能有出入,下面是Kotlin中的一些基本集合类型:

类型介绍
Pair两个值的元组
Triple三个值的元组
Array经过索引的,固定大小的对象和基元集合
List有序的对象集合
Set无序的对象集合
Map键值对对象集合

Kotlin中的视图

在Kotlin引入了视图的概念,简而言之,不同的视图类型会赋予我们对操作集合的不同权限。Kotlin中有两种不同的视图:只读或不可变视图,以及读写或可变视图

比如对于List来说,有两种视图,分别是List和MutableList,前者提供只读视图,后者提供读写视图。当我们用List视图时将无法修改列表,而用MutableList就可以。

    var li = listOf(1,2,3) as MutableList
    li.add(5)

比如我们运行上述代码就会报错,因为listOf函数会产生List视图的集合,这将导致我们无法修改列表,如果我们要续写就需要产生读写视图的列表:

    var li = mutableListOf(1,2,3) 
    li.add(5)

不过本质上这两种视图都是对List的引用,比如说我们可以用List视图引用同一个ArrayList:

    val ar = arrayListOf(1,2,3,4)
    val li:List<Int> = ar
    val li1:MutableList<Int> = ar

不过List视图的引用将无法修改列表本身。

Kotlin中的一些技巧

使用listOf等函数快速创建集合

这个其实在上面给的例子里已经体现了,我们可以使用arrayListOf,listOf等函数快速创建出我们想要的集合而无需再用构造函数。

使用to和mapOf快速创建表

Kotlin中提供了一个to拓展函数,这个函数将生成一个Pair类型的对象,比如

val p1 = "age" to 18

将会创建一个First为"age",Second为18的Pair对象。而这个对象又可以用于mapOf函数。这样我们就可以快速创建一个map,比如:

val mMap = mapOf("age" to 18,"code" to 10086)

这样就创建了一个键值对为< String , Int >类型的map,其中to之前的为Key,之后的为Value。

同时获取索引和值

在Java中,如果我们想要同时获取一个List的索引和值的话可能需要遍历或者采取别的手段来达到这个目的,而在Kotlin中,我们可以用解构来实现这个目的:

fun main() {
    val li = listOf("jack","anderson")
    for((index,value) in li.withIndex()){
        println("index : $index, value:$value")
    }
}

withIndex将返回一个包含键值对的对象,我们将其解构出来就可以同时获得索引和值了。

创建有规律的数组

接下来介绍的是如何创建出一个有规律的数字,比如我们可以创建出一个物的倍数的数组:

fun main() {
    val li = Array(5){index -> index * 5}
    for(value in li){
        println(value)
    }
}

Array括号后面的5是元素个数,index下标是从0开始,我们可以打印出值:
在这里插入图片描述
成功创建了一个包含五的倍数的数组,利用这个技巧我们再加上Array内置的一些方法,就可以实现许多计算,比如我们想要计算从1到5的平方和的话就可以直接这样写:

fun main() {
    val li = Array(5){index -> (index+1) * (index+1)}.sum()
    println(li)
}

使用in

在Java中如果我们想要判断一个元素是否在一个集合中,一般会使用contains方法,不过在Kotlin中提供了in运算符实现了同样的效果:

fun main() {
    val li = Array(5){index -> index*5}
    println(0 in li)
}

实际上在迭代时我们会用到in运算符也是这样。

Kotlin中的逆变和协变

什么是逆变和协变

首先我们需要介绍逆变和协变的概念,协变和逆变都是术语,前者指能够使用比原始指定的派生类型的派生程度更大(更具体的)的类型,后者指能够使用比原始指定的派生类型的派生程度更小(不太具体的)的类型。

以我的理解,协变应该接近于extend,而逆变接近于super。

默认情况下,在Java中泛型强制实行类型不变性–也就是说,如果泛型函数期望一个参数类型T,则不允许替换基类型T或者派生类型T,类型必须是完全预期的类型

实际上,在Java中我也没有对通配符和一般的泛型T的区别和相同有什么很深的理解。我的理解是,通配符?代表不确定的类型,泛型类型T代表确定的类型

类型不变性

这里再介绍一下类型不变性,当一个方法接收到一个类型为T的对象(确定对象,不是泛型对象)时,我们可以传入为T类型或者是T的子类的对象。比如如果一个方法接收一个Animal类型的对象,那么身为Animal子类的Cat类型的对象也可以被传进去。

但是,如果这个方法接收的是一个泛型类型为T的对象,那么将不允许传递派生类型为T的泛型对象。比如,如果可以传递List< Animal >类型的对象,那么将不允许传入List< Cat >类型的对象,这和Java中的类型擦除有关。

书上的一个例子我觉得很形象,比如说我们创建一个Fruit类和两个继承它的类还有一个接收水果的方法:

open class Fruit
class Orange:Fruit()
class Banana:Fruit()

fun receiveFruits(fruits:Array<Fruit>){
    println("水果的数量是${fruits.size}")
}

这个方法可以接受泛型类型为Fruit的数组,如果我们传入Orange或者Banana会怎么样呢?
在这里插入图片描述
可以看到,编译器提示类型不匹配了。香蕉是从水果继承而来的,但是显然一篮子香蕉不是从一篮子水果继承而来的

不过一旦我们用list视图来操作,上述代码就不会报错了:
在这里插入图片描述
这是因为List视图只允许我们进行读而不允许我们进行写,这样是安全的。在Kotlin中,这个效果是由于List视图是out修饰的,我们将在后面的协变中介绍。
在这里插入图片描述

使用协变

上边介绍到了,一旦我们使用List视图,那么receiveFruits方法就可以被调用了,这正是由于使用了协变的原因。接下来我们创建一个方法来模拟协变的使用场景,比如我们想要把一个Fruit的Array复制到另一个Fruit的Array中:

fun copyFromTo(from:Array<Fruit>,to:Array<Fruit>){
    for(i in 0 until from.size){
        to[i] = from[i]
    }
}

这种情况下我们显然不能传入除Fruit类之外的泛型类,比如:
在这里插入图片描述
编译器是不会允许我们传入泛型类型为Banana的参数给from的,这个时候我们只需要修改一下这个函数,在传入的from参数处使用协变即可:

fun copyFromTo(from:Array<out Fruit>,to:Array<Fruit>){
    for(i in 0 until from.size){
        to[i] = from[i]
    }
}

在这里插入图片描述
这样编译器就不会报错了。要理解这个协变的含义我们可以从编译器为什么不让我们传入Banana类型的参数看。如果我们可以传入Banana类型的参数,我们就有可能对Banana执行一些Fruit层面的指令。

举个例子来说,大部分水果冲洗完成之后就可以直接食用了,但是香蕉的果皮较厚,我们就不能直接食用,在这之前还需要剥皮。身为子类的Banana🍌肯定是有其特殊之处的,不能用基类Fruit的一些操作直接用在Banana上。但是如果我们不对这个Banana进行操作的话,那么就不会有什么大问题了,这就是协变的含义。

这里对copyFromTo方法的from参数加上out参数后就说明我们不会对这个from参数进行任何方法的调用了,我们只是单单读取这个参数,这样编译器就允许我们传入Fruit的子类的泛型类型了,换言之,我们就实现了协变。这种在使用泛型类型时使用协变的行为称之为“使用点型变”。

使用逆变

与协变相对的就是逆变了,如果说协变是只读不写的话,那么逆变就是只写不读。实际上也确实是这样,使用逆变将允许我们在该参数上进行设置值的方法调用,而不允许读取的方法。

我们依旧以上面的copyFromTo方法为例,现在我们希望可以将任意Fruit或者Fruit子类的元素复制到Fruit或Fruit超类的集合中,比如说我们传一个Any类的参数:
在这里插入图片描述
显然由于类型不变性这样是行不通的,在这里我们再次对copyFromTo方法做修改,这次我们对to参数使用逆变:

fun copyFromTo(from:Array<out Fruit>,to:Array<in Fruit>){
    for(i in 0 until from.size){
        to[i] = from[i]
    }
}

在这里插入图片描述
这样编译器就允许我们这样调用了。

使用Where的参数类型约束

这部分内容说白了就是约束泛型类型的范围,比如说我们有一个方法需要传入一个泛型类,这个泛型类需要实现AutoCloseable接口,那么我们就可以这样写

fun <T:AutoCloseable> useAndClose(input:T)
{
    input.close()
}

实际上上面的和Java中的写法也差不多,不过如果是一个泛型需要实现多个接口的话就不能这么写了,需要我们用where参数进行约束:

fun <T> useAndClose(input:T)
    where T:AutoCloseable,
        T:Appendable
{
    input.append("haha")
    input.close()
}

where约束跟在参数列表后面,花括号前面。约束参数中用逗号分隔。

星投影

星投影用<*>定义参数类型,它是指定泛型只读类型和原始类型的Kotlin等效物,**简单来说,我们可以用星投影捕获泛型类型,但是我们只能对捕获的泛型类型进行读取而不能修改。**当你想表达对类型不太了解但有希望类型安全时,请使用星投影,星投影只允许读出而不允许写入,比如:

fun printValues(values:Array<*>){
    for(value in values){
        println(value)
    }
}

在这个方法中我们用星投影捕获了泛型类型,但是我们只能读取values值不能写入或者更改values值,实际上就相当于out T,但是写起来更简洁。

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

word内怎么快速选择图片

前一阵子想把图片中央对齐&#xff0c;这就需要一点一点向下划那个滚轮&#xff0c;非常麻烦。 新建个文档演示下&#xff1a; 这样的内容一共有三页。太乱&#xff1f;不我觉得我平时看文献得时候脑子里就是上面这个情况&#xff0c;很有代入感。 选择选择&#xff0c;在左侧出…

QGraphicsView 实例3地图浏览器

主要介绍Graphics View框架&#xff0c;实现地图的浏览、放大、缩小&#xff0c;以及显示各个位置的视图、场景和地图坐标 效果图: mapwidget.h #ifndef MAPWIDGET_H #define MAPWIDGET_H #include <QLabel> #include <QMouseEvent> #include <QGraphicsView&…

vue实现文件上传,前后端

前端封装el-upload组件&#xff0c;父组件传值dialogVisible&#xff08;用于显示el-dialog&#xff09;&#xff0c;子组件接收&#xff0c;并且关闭的时候返回一个值&#xff08;用于隐藏el-dialog&#xff09;,最多上传五个文件&#xff0c;文件格式为.jpg\pdf\png <tem…

Redis缓存问题(穿透, 击穿, 雪崩, 污染, 一致性)

目录 1.什么是Redis缓存问题&#xff1f; 2.缓存穿透 3.缓存击穿 4.缓存雪崩 5.缓存污染&#xff08;或满了&#xff09; 5.1 最大缓存设置多大 5.2 缓存淘汰策略 6.数据库和缓存一致性 6.1 4种相关模式 6.2 方案&#xff1a;队列重试机制 6.3 方案&#xff1a;异步更新缓…

Lnton羚通关于Optimization在【PyTorch】中的基础知识

OPTIMIZING MODEL PARAMETERS &#xff08;模型参数优化&#xff09; 现在我们有了模型和数据&#xff0c;是时候通过优化数据上的参数来训练了&#xff0c;验证和测试我们的模型。训练一个模型是一个迭代的过程&#xff0c;在每次迭代中&#xff0c;模型会对输出进行猜测&…

mqtt开关实现

这个项目的主要需求其实并不复杂&#xff0c;只是需要让用户可以在小程序上控制预约后的自习室座位的灯和柜子等的开关。这里的关键是需要通过一个网络应用来转发用户对智能硬件的控制请求。 物联网应用的主要几个难点及对应的思路如下&#xff1a; 通信数据量小、通信环境不…

优酷视频码率、爱奇艺视频码率、B站视频码率、抖音视频码率对比

优酷视频码率、爱奇艺视频码率与YouTube视频码率对比 优酷视频码率&#xff1a; 优酷的视频码率可以根据视频质量、分辨率和内容类型而变化。一般而言&#xff0c;优酷提供了不同的码率选项&#xff0c;包括较低的标清&#xff08;SD&#xff09;码率和较高的高清&#xff08;…

[Openwrt-21.02]MT7981 增加 USB RNDIS功能支持操作说明

环境说明 ubuntu18.04编译环境,openwrt-21.02版本,MT7981开发板 openwrt配置项 make menuconfig配置 ​​ ​​​​​​ 配置后.config配置 CONFIG_PACKAGE_kmod-usb-core=y CONFIG_PACKAGE_kmod-usb-ehci=y CONFIG_PACKAGE_kmod-usb-net=y CONFIG_PACKAGE_kmod-usb-net-…

centOS7.6虚拟机设置桥接方式联网

1、虚拟机设置 设置添加进来的虚拟机&#xff0c;选择“网络适配器”&#xff0c;网络连接方式选择“桥接模式”。点击确定。 2、虚拟网络编辑器设置 VMware中选择编辑中的“虚拟机网络编辑器”&#xff0c;选中桥接模式&#xff0c;“已桥接至”选择当前本机电脑的网络信息。…

百度云BOS云存储的图片如何在访问时,同时进行格式转换、缩放等处理

前言 之前做了一个图片格式转换和压缩的服务&#xff0c;结果太占内存。后来查到在访问图片链接时&#xff0c;支持进行图片压缩和格式转换&#xff0c;本来想着先格式转换、压缩图片再上传到BOS&#xff0c;现在变成了上传后&#xff0c;访问时进行压缩和格式转换。想了想&am…

【java】为什么文件上传要转成Base64?

文章目录 1 前言2 multipart/form-data上传3 Base64上传3.1 Base64编码原理3.2 Base64编码的作用 4 总结 1 前言 最近在开发中遇到文件上传采用Base64的方式上传&#xff0c;记得以前刚开始学http上传文件的时候&#xff0c;都是通过content-type为multipart/form-data方式直接…

虫情测报系统的工作原理及功能优势

KH-CQPest虫情测报系统能够在不对虫体造成任何破坏的情况下&#xff0c;无公害的杀死虫子&#xff0c;利用高倍显微镜和高清摄像头拍摄虫体照片&#xff0c;并将虫体照片发送到远端平台&#xff0c;让工作人员无需要到现场&#xff0c;通过平台就可以观察害虫的种类和数量&…

Visual Studio 2022离线源码编译onnxruntime

1. 首先参考前述文章《Visual Studio 2019源码编译cpu版本onnxruntime_xunan003的博客-CSDN博客》第1~3步&#xff0c;将anaconda python3.8虚拟环境copy至内网离线环境envs中。 并将下载的onnxruntime包迁移至内网固定位置&#xff1b; 2.查看onnxruntime/cmake/external所依…

USB3.2链路训练及状态机解析

1.简介 LTSSM(Link Training and Status State Machine)定义了USB3.2总线链路层连接性及链路层电源管理。LTSSM由12种不同的链路状态组成&#xff0c;可以根据它们的功能对其进行表征。 LTSSM有4个可操作的link状态&#xff0c;分别为U0、U1、U2及U3。U0是使能Enhanced Super…

Spring框架中JavaBean的生命周期及单例模式与多列模式

Spring框架中JavaBean的生命周期及单例模式与多列模式 1. Spring框架中JavaBean的管理过程1.1 #定义Bean1.2 Bean的实例化1.3 属性注入1.4 初始化方法1.5 Bean的使用和引用1.6 销毁方法 2. 单例模式与原型模式在JavaBean管理中的应用1.在Spring管理JavaBean的过程中&#xff0c…

STM32 CubeMX (第三步Freertos中断管理和软件定时)

STM32 CubeMX STM32 CubeMX &#xff08;第三步Freertos中断管理和软件定时&#xff09; STM32 CubeMX一、STM32 CubeMX设置时钟配置HAL时基选择TIM1&#xff08;不要选择滴答定时器&#xff1b;滴答定时器留给OS系统做时基&#xff09;使用STM32 CubeMX 库&#xff0c;配置Fre…

Java请求Http接口-hutool的HttpUtil(超详细-附带工具类)

概述 HttpUtil是应对简单场景下Http请求的工具类封装&#xff0c;此工具封装了HttpRequest对象常用操作&#xff0c;可以保证在一个方法之内完成Http请求。 此模块基于JDK的HttpUrlConnection封装完成&#xff0c;完整支持https、代理和文件上传。 导包 <dependency>&…

第二章MyBatis入门程序

入门程序 创建maven程序 导入MyBatis依赖。pom.xml下导入如下依赖 <dependencies><dependency><groupId>org.mybatis</groupId><artifactId>mybatis</artifactId><version>3.5.6</version></dependency><dependen…

vue3 简易用对话框实现点击头像放大查看

设置头像悬停手势 img:hover{cursor: pointer;}效果&#xff1a; 编写对话框 <el-dialog class"bigAvatar"style"border-radius: 4px;"v-model"deleteDialogVisible"title"查看头像"top"5px"><div><img src&…

[python] Kmeans文本聚类算法+PAC降维+Matplotlib显示聚类图像

0 前言 本文主要讲述以下几点&#xff1a; 1.通过scikit-learn计算文本内容的tfidf并构造N*M矩阵(N个文档 M个特征词)&#xff1b; 2.调用scikit-learn中的K-means进行文本聚类&#xff1b; 3.使用PAC进行降维处理&#xff0c;每行文本表示成两维数据&…