20:kotlin 类和对象 --泛型(Generics)

类可以有类型参数

class Box<T>(t: T) {var value = t
}

要创建类实例,需提供类型参数

val box: Box<Int> = Box<Int>(1)

如果类型可以被推断出来,可以省略

val box = Box(1)

通配符

在JAVA泛型中有通配符?? extends E? super E,在kotlin中没有这个概念,取而代之的是Declaration-site variancetype projections

Declaration-site variance

out 协变

对于如下代码

interface Source<T> {fun next():T
}fun demo(x : Source<Number>){val objects: Source<Any> = x
}

编译器报错 – 类型不匹配
在这里插入图片描述

想要代码成立需要在泛型定义时使用out

interface Source<T> {fun next():T
}fun demo(x : Source<Number>){val objects: Source<Any> = x
}

in 逆变

对于代码

interface Comparable<T> {operator fun compareTo(other: T): Int
}fun demo(x: Comparable<Number>) {x.compareTo(1.0)val y: Comparable<Int> = x
}

报错类型不匹配
在这里插入图片描述
想要代码成立需要在泛型定义时使用in

interface Comparable<in T> {operator fun compareTo(other: T): Int
}fun demo(x: Comparable<Number>) {x.compareTo(1.0)val y: Comparable<Int> = x
}

如果T类型作为参数(消费)就是用in,如果作为返回值(生产)就用out
Consumer in, Producer out!
逆变就是大类型变小类型,协变就是小类型变大类型

type projections(类型投影)

Use-site variance: type projections(使用点位变异:类型投影)

对于以下代码

class Demo<T>{fun copy(from: Array<T>, to: Array<T>) {assert(from.size == from.size)for (i in from.indices) {to[i] = from.get(i)}}
}fun main() {val str: Array<String> = arrayOf("hello", "world")val obj: Array<Any> = arrayOf(123, 432)Demo<Any>().copy(str, obj)
}

报错
在这里插入图片描述
在类声明时,不管是使用class Demo<in T>还是class Demo<out T>都会报错,为解决这种情况,可以修改copy方法的from参数类型为from: Array<out T>,因为from作为生产者,生产数组中的值

class Demo<T>{fun copy(from: Array<out T>, to: Array<T>) {assert(from.size == from.size)for (i in from.indices) {to[i] = from.get(i)}}
}

当然也可以改成下边写法

class Demo<T>{fun copy(from: Array<T>, to: Array<in T>) {assert(from.size == from.size)for (i in from.indices) {to[i] = from.get(i)}}
}fun main() {val str: Array<String> = arrayOf("hello", "world")val obj: Array<Any> = arrayOf(123, 432)Demo<String>().copy(str, obj)
}

星号投影(*)

有时候参数是一个泛型类型,但是在定义方法的时候不能确定泛型的具体类型,需要用到*投影
语法如下

  • 对于泛型类型Foo<out T : TUpper>T是一个具有上界TUpper的协变类型参数,Foo<*>等价于Foo<out TUpper>。这意味着当T未知时,你可以安全地从Foo<*>中读取TUpper的值。
  • 对于泛型类型Foo<in T>T是一个逆变类型参数,Foo<*>等价于Foo<in Nothing>。这意味着当T未知时,你无法以安全的方式向Foo<*>写入任何值。
  • 对于泛型类型Foo<T : TUpper>T是一个不变类型参数,具有上界TUpperFoo<*>在读取值时等价于Foo<out TUpper>,在写入值时等价于Foo<in Nothing>

举个例子

class Box<out T : Any>(private val value: T) {fun getValue(): T {return value}
}fun printBoxValue(box: Box<*>) {val value = box.getValue()println(value)
}fun main(){printBoxValue(Box(123)) // 123printBoxValue(Box("hello world"))   // hello world
}

printBoxValue方法的参数使用*投影

如果一个泛型类型有多个类型参数,每个参数可以独立进行投影

泛型函数

不仅类可以有类型参数,函数也可以有类型参数。类型参数位于函数名称之前

fun <T> singletonList(item: T): List<T> {// ...
}fun <T> T.basicToString(): String { // 扩展函数// ...
}

要调用泛型函数,在调用点的函数名称之后指定类型参数

val l = singletonList<Int>(1)

如果可以从上下文中推断出类型参数,则可以省略类型参数

val l = singletonList(1)

泛型约束

对于给定的类型参数,可以通过泛型约束来限制可替代的所有可能类型。

最常见的约束类型是上界(upper bounds)

fun <T : Comparable<T>> sort(list: List<T>) {  ... }

在冒号后指定的类型是上界,表示只有Comparable<T>的子类型可以替代T

sort(listOf(1, 2, 3)) // 正确。Int是Comparable<Int>的子类型
sort(listOf(HashMap<Int, String>())) // 错误:HashMap<Int, String>不是Comparable<HashMap<Int, String>>的子类型

如果不指定上界,则默认为Any?类型的

当一个类型参数需要满足多个上界时,需要使用 where 子句来指定这些上界条件

fun <T> processValues(list: List<T>) where T : CharSequence, T : Comparable<T> {val sortedValues = list.sorted()for (value in sortedValues) {println(value)}
}val stringList: List<String> = listOf("apple", "banana", "cherry")
val intList: List<Int> = listOf(1, 2, 3)
val mixedList: List<Any> = listOf("hello", 42, true)processValues(stringList) // apple, banana, cherry
processValues(intList) // 报错 -- Int 不满足 CharSequence 的上界
processValues(mixedList) // 报错 --Any 不满足 CharSequence 的上界

绝对非空类型(Definitely non-nullable types)

为了方便和java接口和类交互,如果有如下java接口

import org.jetbrains.annotations.*;public interface Game<T> {public T save(T x) {}@NotNullpublic T load(@NotNull T x) {}
}

要继承该接口并重写load方法,使用& Any来声明一个非空参数

interface ArcadeGame<T1> : Game<T1> {override fun save(x: T1): T1// T1 is definitely non-nullableoverride fun load(x: T1 & Any): T1 & Any
}

如果是纯kotlin项目,不需要使用此方法声明,kotlin的类型推断会做这件事

class ArcadeGame<T> {fun load(x: T & Any){println(x)}
}fun main(){ArcadeGame<String?>().load(null)    // 这里String?即使可以为空,调用load方法时传入null依旧报错
}

类型擦除

kotlin对泛型声明的类型安全检查是在编译时进行的。在运行时,泛型类型的实例不保存有关其实际类型参数的任何信息。这种类型信息被称为擦除。例如,Foo<Bar>Foo<Baz?> 的实例在擦除后变为 Foo<*>

泛型类型的检查和转换

由于类型擦除的存在,不能使用is进行如下检查

class ArcadeGame<T>{fun check(x:Any){if (x is T){}   // 报错 -- Cannot check for instance of erased type: T}
}fun main() {val game = ArcadeGame<String?>()if (game is ArcadeGame<String>) {} // 报错 -- Cannot check for instance of erased type: ArcadeGame<String>
}

可以使用型号投影进行检查

class ArcadeGame<T>fun main() {val game = ArcadeGame<String?>()if (game is ArcadeGame<*>) {} 
}

对于x is T这种检查方式,可以进行如下改造

class ArcadeGame<T>(private val type: Class<T>) {fun check(x: Any) {if (type.isInstance(x)) {// x 是 T 类型的实例}}
}fun main() {val game = ArcadeGame<String>(String::class.java)
}

The type arguments of generic function calls are also only checked at compile time. Inside the function bodies, the type parameters cannot be used for type checks, and type casts to type parameters (foo as T) are unchecked. The only exclusion is inline functions with reified type parameters, which have their actual type arguments inlined at each call site. This enables type checks and casts for the type parameters. However, the restrictions described above still apply for instances of generic types used inside checks or casts. For example, in the type check arg is T, if arg is an instance of a generic type itself, its type arguments are still erased.

inline fun <reified A, reified B> Pair<*, *>.asPairOf(): Pair<A, B>? {if (first !is A || second !is B) return nullreturn first as A to second as B
}val somePair: Pair<Any?, Any?> = "items" to listOf(1, 2, 3)val stringToSomething = somePair.asPairOf<String, Any>()
val stringToInt = somePair.asPairOf<String, Int>()
val stringToList = somePair.asPairOf<String, List<*>>()
val stringToStringList = somePair.asPairOf<String, List<String>>() // Compiles but breaks type safety!
// Expand the sample for more details

未经检查的类型转换(Unchecked casts)

对于泛型类型转换,无法在运行时进行检查。

fun gen(): Map<String, *> {return mapOf("one" to "你好", "two" to 123)
}fun main() {val gen = gen()gen as Map<Int, Int>	// 提示 -- Unchecked cast: Map<String, *> to Map<Int, Int>println(gen)	// {one=你好, two=123}
}

因为类型擦除的缘故,gen as Map<Int, Int>并不会报错,只是在编译期做出提醒

如果是这样转换则会报错

fun main() {val gen = gen()gen["one"] as Int   // 报错 -- java.lang.ClassCastException: class java.lang.String cannot be cast to class java.lang.Integer (java.lang.String and java.lang.Integer are in module java.base of loader 'bootstrap')println(gen)
}

如果不想提示,使用注解@Suppress("UNCHECKED_CAST")

fun main() {val gen = gen()@Suppress("UNCHECKED_CAST")gen as Map<Int, Int>println(gen)
}

JVM上,数组类型保留有关其元素被擦除的类型的信息,并且对数组类型的类型转换进行了部分检查:元素类型的可为空性和实际类型参数仍然被擦除。

fun gen(): Array<*> {return arrayOf("hello", "world")
}fun main() {val gen = gen()gen as Array<Int>   //  java.lang.ClassCastException: class [Ljava.lang.String; cannot be cast to class [Ljava.lang.Integer; ([Ljava.lang.String; and [Ljava.lang.Integer; are in module java.base of loader 'bootstrap')println(gen)
}

类型参数的下划线操作符

当其他类型被显式指定时,可以使用下划线操作符来自动推断参数的类型

abstract class SomeClass<T> {abstract fun execute() : T
}class SomeImplementation : SomeClass<String>() {override fun execute(): String = "Test"
}class OtherImplementation : SomeClass<Int>() {override fun execute(): Int = 42
}object Runner {inline fun <reified S: SomeClass<T>, T> run() : T {return S::class.java.getDeclaredConstructor().newInstance().execute()}
}fun main() {// T 是 String 类型,因为SomeImplementation为SomeClass<String>val s = Runner.run<SomeImplementation, _>()assert(s == "Test")// T 是 Int 类型 ,因为SomeImplementation为SomeClass<Int>val n = Runner.run<OtherImplementation, _>()assert(n == 42)
}

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

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

相关文章

25. K 个一组翻转链表

给你链表的头节点 head &#xff0c;每 k 个节点一组进行翻转&#xff0c;请你返回修改后的链表。 k 是一个正整数&#xff0c;它的值小于或等于链表的长度。如果节点总数不是 k 的整数倍&#xff0c;那么请将最后剩余的节点保持原有顺序。 你不能只是单纯的改变节点内部的值…

自媒体原创改写工具,自媒体首发改写软件

自媒体平台已成为许多创作者表达观点、分享知识和积累影响力的关键渠道。创作是需要技巧和经验的。本文将分享一些自媒体文章改写技巧&#xff0c;并推荐一系列优秀的自媒体文章改写工具&#xff0c;帮助您提升创作效率&#xff0c;创作出更优秀的文章。 自媒体文章改写技巧 …

Backend - Django makemigrations

目录 一、迁移命令 &#xff08;一&#xff09;前提 &#xff08;二&#xff09;生成迁移文件 &#xff08;三&#xff09;执行迁移 二、迁移问题 1. Error&#xff1a;No changes detected 2. Error&#xff1a;You are trying to add a non-nullable field XXX to XXX…

[读论文]BK-SDM: A Lightweight, Fast, and Cheap Version of Stable Diffusion

github: GitHub - Nota-NetsPresso/BK-SDM: A Compressed Stable Diffusion for Efficient Text-to-Image Generation [ICCV23 Demo] [ICML23 Workshop] ICML 2023 Workshop on ES-FoMo 简化方式 蒸馏方式&#xff08;训练Task蒸馏outKD-FeatKD&#xff09; 训练数据集 评测指标…

在intelliJ spring boot gradle插件3.2.0中未找到匹配的变量

我正在尝试使用spring启动Gradle插件的版本3.2.0。这是我的build.gradle文件&#xff1a; plugins {id javaid org.springframework.boot version 3.2.0id io.spring.dependency-management version 1.1.4 }group com.yaxin version 0.0.1-SNAPSHOTjava {sourceCompatibilit…

GPIO的使用--时钟使能含义--代码封装

目录 一、时钟使能的含义 1.为什么要时钟使能&#xff1f; 2.什么是时钟使能&#xff1f; 3.GPIO的使能信号&#xff1f; 二、代码封装 1.封装前完整代码 2.封装结构 封装后代码 led.c led.h key.c key.h main.c 一、时钟使能的含义 1.为什么要时钟使能&#xff1f…

Python开发运维:Python 3.8 常用标准库

目录 一、理论 1.Python3.8 标准库 2.常用标准库 二、问题 1.Python 正则表达式如何实现 一、理论 1.Python3.8 标准库 &#xff08;1&#xff09;官网 Python 标准库 — Python 3.8.17 文档 &#xff08;2&#xff09;其他版本下拉列表查询 2.常用标准库 &#xff0…

MySQL笔记-第01章_数据库概述

视频链接&#xff1a;【MySQL数据库入门到大牛&#xff0c;mysql安装到优化&#xff0c;百科全书级&#xff0c;全网天花板】 文章目录 第01章_数据库概述1. 为什么要使用数据库2. 数据库与数据库管理系统2.1 数据库的相关概念2.2 数据库与数据库管理系统的关系2.3 常见的数据库…

Linux--网络编程-ftp(TCP)网络通信-文件交互

项目要求&#xff1a;实现以下内容 远程控制&#xff1a; 1、查看服务器当前路径文件 ls 3、进入、退出服务器文件夹 cd 4、上传文件到服务器 put xxx 本地控制&#xff1a; 1、查看本地&#xff08;客户端&#xff09;文件 lls 2、进入客户端文件夹 lcd 3、获取服务器的文件…

音频录制软件哪个好?帮助你找到最合适的一款

音频录制软件是日常工作、学习和创作中不可或缺的一部分。选择一个适合自己需求的录音软件对于确保音频质量和提高工作效率至关重要。可是您知道音频录制软件哪个好吗&#xff1f;本文将深入探讨两种常见的音频录制软件&#xff0c;通过详细的步骤指南&#xff0c;帮助您了解它…

编写Java应用程序,输出满足1+2+3+……+n<8888的最大正整数n。

源代码&#xff1a; public class Main { public static void main(String[] args) { int i 1; int sum 0; for(i 1;;i){ sum i; if (sum >8888) break; } System.out.println(i-1); } } 实验运行截图&#xff1a;

【滑动窗口】LeetCode2953:统计完全子字符串

作者推荐 [二分查找]LeetCode2040:两个有序数组的第 K 小乘积 本题其它解法 【离散差分】LeetCode2953:统计完全子字符串 题目 给你一个字符串 word 和一个整数 k 。 如果 word 的一个子字符串 s 满足以下条件&#xff0c;我们称它是 完全字符串&#xff1a; s 中每个字符…

深入理解:指针变量的解引用 与 加法运算

前言 指针变量的解引用和加法运算是非常高频的考点&#xff0c;也是难点&#xff0c;因为对初学者的不友好&#xff0c;这就导致了各大考试都很喜欢在这里出题&#xff0c;通常会伴随着强制类型转换、二维数组、数组指针等一起考查大家对指针的理解。但是不要怕&#xff0c;也许…

论文解读--PointPillars- Fast Encoders for Object Detection from Point Clouds

PointPillars--点云目标检测的快速编码器 摘要 点云中的物体检测是许多机器人应用(如自动驾驶)的重要方面。在本文中&#xff0c;我们考虑将点云编码为适合下游检测流程的格式的问题。最近的文献提出了两种编码器;固定编码器往往很快&#xff0c;但牺牲了准确性&#xff0c;而…

腾讯视频崩了,年终奖没了。。。

最近互联网的瓜可是不少啊&#xff01;最开始阿里云崩了&#xff0c;阿里云崩了之后&#xff0c;没几天滴滴也崩了&#xff0c;滴滴崩了之后&#xff0c;结果昨天腾讯视频也崩了......年底了&#xff0c;都要来刷刷存在感吗&#xff1f; 简直让我想起来一首儿歌&#xff1a; 阿…

使用autodl服务器,两个3090显卡上运行, Yi-34B-Chat-int4模型,并使用vllm优化加速,显存占用42G,速度23 words/s

1&#xff0c;演示视频地址 https://www.bilibili.com/video/BV1Hu4y1L7BH/ 使用autodl服务器&#xff0c;两个3090显卡上运行&#xff0c; Yi-34B-Chat-int4模型&#xff0c;用vllm优化&#xff0c;增加 --num-gpu 2&#xff0c;速度23 words/s 2&#xff0c;使用3090显卡 和…

25、矩阵乘法的本质

本来一直在介绍卷积,为什么突然出现一个矩阵乘法呢? 因为如果我们将卷积运算拆开,其中最核心的部分便是一个矩阵乘法。所以,卷积算法可以看做是带滑窗的矩阵乘法。 这里的滑窗,就是卷积运算中所示意的动图那样,所以,我们把滑窗固定,不看卷积核滑动这个动作,那么就是…

龙芯loongarch64服务器编译安装tokenizers

1、简介 Hugging Face 的 Tokenizers 库提供了一种快速和高效的方式来处理(即分词)自然语言文本,用于后续的机器学习模型训练和推理。这个库提供了各种各样的预训练分词器,如 BPE、Byte-Pair Encoding (Byte-Level BPE)、WordPiece 等,这些都是现代 NLP 模型(如 BERT、GP…

VT-MRPA1-151-1X/V0/0控制2FRE16模块式模拟放大器

适用于控制带有电气位置反馈的直动式比例减压阀&#xff08;DBETR- 1X 类型&#xff09;或带有电气位置反馈的比例流量控制阀&#xff08;2FRE... 类型&#xff09;&#xff1b;控制值输入 1 0 V&#xff08;差动输入&#xff09;&#xff1b; 可分别调节“上/下”斜坡时间的斜…