理解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

相关内容

热门资讯

前端-session、jwt 目录:   (1)session (2&#x...
linux入门---制作进度条 了解缓冲区 我们首先来看看下面的操作: 我们首先创建了一个文件并在这个文件里面添加了...
关于测试,我发现了哪些新大陆 关于测试 平常也只是听说过一些关于测试的术语,但并没有使用过测试工具。偶然看到编程老师...
前缀和与对数器与二分法 1. 前缀和 假设有一个数组,我们想大量频繁的去访问L到R这个区间的和,...
nodejs:本地安装nvm实... 一、背景-使用不同版本node的原因 vue3+ts、nuxt3版本,node...
JAVA集合知识整理 Java集合知识整理 HashMap相关 HashMap的底层数据结构:jdk1.8之...
无刷直流电机介绍及单片机控制实... 无刷直流电机介绍及单片机控制实例前言基本概念优势与劣势使用寿命基本结构使用单片机控制实例电子调速器&...
fwdiary(2) dp2 1.传纸条  AcWing 275. 传纸条 - AcWing 走两条路,走一条最大的...
常用的DOS命令 常用的DOS命令 DOS(Disk Operating System,磁...
<C++> 类和对象(下) 1.const成员函数将const修饰的“成员函数”称之为const成员函数,cons...