理解Kotlin泛型
创始人
2024-05-03 19:52:02
0

文章目录

      • Kotlin泛型
        • 声明处型变
          • 协变< out T>
          • 逆变< in T>
        • 使用处型变(类型投影)
      • 参考

Kotlin泛型

声明处型变

协变< out T>
interface GenericsP {fun get(): T  //读取并返回T,可以认为只能读取T的对象是生产者
}

如上声明了GenericsP< T>接口,如果其内部只能读取并返回T,可以认为GenericsP实例对象为生产者(返回T)。继续声明如下实体类:

open class Book(val name: String)
data class EnglishBook(val english: String) : Book(english)
data class MathBook(val math: String) : Book(math)

已知EnglishBook、MathBook为Book的子类,通常可以像下面这样声明:

val book: Book = EnglishBook("英语")

一般在实现多态时会这么写,但是如果将Book、EnglishBook当成泛型放入GenericsP,他们之间的关系还成立吗?即:

error信息
可以看到编译器直接报错,因为虽然EnglishBook是Book的子类,但是GenericsP< EnglishBook>并不是GenericsP< Book>的子类,如果想让这个关系也成立,Kotlin提供了out修饰符,out修饰符能够确保:

1、T只能用于函数返回中,不能用于参数输入中;
2、GenericsP< EnglishBook>可以安全的作为GenericsP< Book>的子类

示例如下:

interface GenericsP {fun get(): T  //读取并返回T,可以认为只能读取T的对象是生产者// fun put(item: T) //错误,不允许在输入参数中使用
}

经过如上的改动后,可以看到GenericsP< EnglishBook>可以正确赋值给GenericsP< Book>了:
正确赋值

逆变< in T>
interface GenericsC {fun put(item: T) //写入T,可以认为只能写入T的对象是消费者
}

如上声明了GenericsC< T>接口,如果其内部只能写入T,可以认为GenericsC实例对象为消费者(消费T)。为了保证T只能出现在参数输入位置,而不能出现在函数返回位置上,Kotlin可以使用in进行控制:

interface GenericsC {fun put(item: T) //写入T,可以认为只能写入T的对象是消费者//fun get(): T  //错误,不允许在返回中使用
}

继续声明如下函数:

    /*** 称为GenericsC在Book上是逆变的。* 跟系统源码中的Comparable类似*/private fun consume(to: GenericsC) {//GenericsC实例赋值给了GenericsCval target: GenericsC = totarget.put(EnglishBook("英语"))}

可以看到GenericsC中的泛型参数声明为in后,GenericsC实例可以直接赋值给了GenericsC< EnglishBook>,称为GenericsC在Book上是逆变的。在系统源码中我们经常使用的一个例子就是Comparable:

//Comparable.kt
public interface Comparable {public operator fun compareTo(other: T): Int
}

使用处型变(类型投影)

上一节中in、out都是写在类的声明处,从而控制泛型参数的使用场景,但是如果泛型参数既可能出现在函数入参中,又可能出现在函数返回中,典型的类就是Array:

class Array(val size: Int) {fun get(index: Int): T { …… }fun set(index: Int, value: T) { …… }
}

这时候就不能在声明处做任何协变/逆变的操作了,如下函数中使用Array:

    fun copy(from: Array, to: Array) {if (from.size != to.size) returnfor (i in from.indices)to[i] = from[i]}

调用方:

val strs: Array = arrayOf("1", "2")
val any = Array(2) {}
copy(strs, any) //编译器报错 strs其类型为 Array 但此处期望 Array

参考

【1】https://www.kotlincn.net/docs/reference/generics.html
【2】https://mp.weixin.qq.com/s/vSwx7fgROJcrQwEOW7Ws8A
【3】https://juejin.cn/post/7042606952311947278

相关内容

热门资讯

保存时出现了1个错误,导致这篇... 当保存文章时出现错误时,可以通过以下步骤解决问题:查看错误信息:查看错误提示信息可以帮助我们了解具体...
汇川伺服电机位置控制模式参数配... 1. 基本控制参数设置 1)设置位置控制模式   2)绝对值位置线性模...
不能访问光猫的的管理页面 光猫是现代家庭宽带网络的重要组成部分,它可以提供高速稳定的网络连接。但是,有时候我们会遇到不能访问光...
表格中数据未显示 当表格中的数据未显示时,可能是由于以下几个原因导致的:HTML代码问题:检查表格的HTML代码是否正...
本地主机上的图像未显示 问题描述:在本地主机上显示图像时,图像未能正常显示。解决方法:以下是一些可能的解决方法,具体取决于问...
不一致的条件格式 要解决不一致的条件格式问题,可以按照以下步骤进行:确定条件格式的规则:首先,需要明确条件格式的规则是...
表格列调整大小出现问题 问题描述:表格列调整大小出现问题,无法正常调整列宽。解决方法:检查表格的布局方式是否正确。确保表格使...
Android|无法访问或保存... 这个问题可能是由于权限设置不正确导致的。您需要在应用程序清单文件中添加以下代码来请求适当的权限:此外...
【NI Multisim 14...   目录 序言 一、工具栏 🍊1.“标准”工具栏 🍊 2.视图工具...
银河麒麟V10SP1高级服务器... 银河麒麟高级服务器操作系统简介: 银河麒麟高级服务器操作系统V10是针对企业级关键业务...