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.rhkb.cn/news/210173.html

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

相关文章

eNSP实验

前言 本文记录了使用eNSP进行组网&#xff0c;学习、巩固一些之前学的网络基础知识和协议。实验中用到的eNSP工程源文件在下方仓库中。 门牙会稍息 / eNSP GitCode 一&#xff1a;同网段、网关互通 网络拓扑如下&#xff1a; AR1的配置&#xff1a; interface G0/0/0 ip a…

C# | 使用AutoResetEvent和ManualResetEvent进行线程同步和通信

使用AutoResetEvent和ManualResetEvent进行线程同步和通信 文章目录 使用AutoResetEvent和ManualResetEvent进行线程同步和通信介绍AutoResetEventManualResetEvent 异同点使用场景和代码示例AutoResetEvent 使用示例ManualResetEvent 使用示例阻塞多个线程并同时激活 介绍 在…

dockerfile文件:copy和add 异同

相同点&#xff1a; 复制文件或目录&#xff1a; 无论是 COPY 还是 ADD 都可以将文件或目录从构建上下文复制到容器中。支持源路径和目标路径&#xff1a; 两者都需要指定源路径和目标路径&#xff0c;用于指定要复制的文件或目录在主机上的位置以及在容器中的目标路径。 不同…

基于javaweb的宠物服务商城系统设计与开发

摘 要 最近几年以来&#xff0c;宠物在人们的日常生活中所占的地位越来越重要了&#xff0c;它们不仅仅是我们的朋友&#xff0c;也成为了我们家庭中的一份子。21世纪&#xff0c;信息技术飞速发展&#xff0c;计算机行业日新月异&#xff0c;极大地带动了信息的流动&#xff…

多线程(初阶六:单例模式)

目录 一、单例模式的简单介绍 二、饿汉模式 三、懒汉模式 四、饿汉模式和懒汉模式的线程安全问题分析 1、饿汉模式&#xff08;线程安全&#xff09; 2、懒汉模式&#xff08;线程不安全&#xff09; 解决懒汉模式的线程安全问题 ①给写操作打包成原子 ②去除冗余操作 …

Mendix组件推荐:灵活的在线表格

- 视频 mendix在线表格.mp4 20.95MB - 客户需求 如果你是一个中小型企业的负责人&#xff0c;你可能面临着&#xff1a; 多人协作录入数据展示数据库中的数据对数据安全有要求、希望本地离线部署并且IT人员配置有限等挑战 为了更好地管理你的业务数据&#xff0c;你需要一个…

zabbix 监控

zabbit 监控 非常成熟的监控软件。 运维人员&#xff0c;尽快系统服务器的状态&#xff0c;网站的流量&#xff0c;服务进程的运行状态。 保证整个集群的工作正常。7*24 zabbix是什么&#xff1a; web界面提供的一种可视化监控服务软件。 分布式的方式系统监控以及网络监控…

MySQL备份与恢复

一、逻辑备份 1、回顾什么是逻辑备份 逻辑备份就是把数据库、数据表或者数据进行导出&#xff0c;导出到一个文本文件中。 2、逻辑备份工具 mysqldump&#xff1a;提供全库级、数据库级别以及表级别的数据备份 mysqldump binlog二进制日志实现增量备份 3、逻辑的导出与导…

Spring Bean的生命周期各阶段详解附源码

目录 Bean的生命周期Bean定义阶段Bean实例化阶段Bean属性注入阶段Bean初始化阶段Bean销毁阶段 Bean的生命周期 bean的生命周期&#xff0c;我们都知道大致是分为&#xff1a;bean定义&#xff0c;bean的实例化&#xff0c;bean的属性注入&#xff0c;bean的初始化以及bean的销毁…

使用com组件编辑word

一个普通的窗体应用&#xff0c;6个button using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; using System.Drawing; using System.Linq; using System.Text; using System.Threading.Tasks; using System.Windows.Forms; u…

【C/PTA —— 14.结构体1(课内实践)】

C/PTA —— 14.结构体1&#xff08;课内实践&#xff09; 6-1 计算两个复数之积6-2 结构体数组中查找指定编号人员6-3 综合成绩6-4 结构体数组按总分排序 6-1 计算两个复数之积 struct complex multiply(struct complex x, struct complex y) {struct complex product;product.…

探索人工智能领域——每日20个名词详解【day8】

目录 前言 正文 总结 &#x1f308;嗨&#xff01;我是Filotimo__&#x1f308;。很高兴与大家相识&#xff0c;希望我的博客能对你有所帮助。 &#x1f4a1;本文由Filotimo__✍️原创&#xff0c;首发于CSDN&#x1f4da;。 &#x1f4e3;如需转载&#xff0c;请事先与我联系以…

聊聊 Jetpack Compose 的 “状态订阅自动刷新” -- mutableStateListOf

Jekpack Compose “状态订阅&自动刷新” 系列&#xff1a; 【 聊聊 Jetpack Compose 的 “状态订阅&自动刷新” - - MutableState/mutableStateOf 】 【 聊聊 Jetpack Compose 的 “状态订阅&自动刷新” - - remember 和重组作用域 】 【 聊聊 Jetpack Compose 的 …

互联网Java工程师面试题·Spring Boot篇·第一弹

目录 1、什么是 Spring Boot&#xff1f; 2、Spring Boot 有哪些优点&#xff1f; 3、什么是 JavaConfig&#xff1f; 4、如何重新加载 Spring Boot 上的更改&#xff0c;而无需重新启动服务器&#xff1f; 5、Spring Boot 中的监视器是什么&#xff1f; 6、如何在 Sprin…

Bishop新著 - 深度学习:基础与概念 - 前言

译者的话 十几年前&#xff0c;笔者在MSRA实习的时候&#xff0c;就接触到了Christopher M, Bishop的经典巨著《Pattern Recogition and Machine Learning》(一般大家简称为PRML)。Bishop大神是微软剑桥研究院实验室主任&#xff0c;物理出身&#xff0c;对机器学习的基本概念…

根文件系统初步测试

一. 简介 上一篇文章学习了向所编译生成的根文件系统中加入 lib库文件。文章地址如下&#xff1a; 根文件系统lib库添加与初步测试-CSDN博客 本文继上一篇文章的学习&#xff0c;本文对之前制作的根文件系统进行一次初步测试。 二. 根文件系统初步测试 为了方便测试&#…

Oracle 11g安装过程

文章目录 前言1.下载安装包2.安装2.1本地安装文件2.2 安装过程 3.查看是否安装成功3.1 查看oracle是否安装成功3.2 查看oracle服务 前言 本文仅用于记录亲自安装oracle的过程 1.下载安装包 官网地址&#xff1a; Oracle Database 11g Release 2 (11.2.0.1.0) 注意&#xff…

08、分析测试执行时间及获取pytest帮助

官方用例 # content of test_slow_func.py import pytest from time import sleeppytest.mark.parametrize(delay,(1.1,1.2,1.3,1.4,1.5,1.6,1.7,1.8,1.9,1.0,0.1,0.2,0,3)) def test_slow_func(delay):print("test_slow_func {}".format(delay))sleep(delay)assert…

java SSM毕业生信息管理myeclipse开发mysql数据库springMVC模式java编程计算机网页设计

前言 学校的规模不断扩大&#xff0c;学生数量急剧增加&#xff0c;有关学生的各种信息量也成倍增长。面对庞大的信息量需要有学生信息管理系统来提高学生管理工作的效率。通过这样的系统可以做到信息的规范管理、科学统计和快速查询、修改、增加、删除等&#xff0c;从而减少管…

韶音开放式耳机怎么样?Oladance耳机怎么样?三大开放式对比!

当代耳机的痛点之一&#xff1a;舒适度&#xff0c;为什么我会这么说呢&#xff1f;大家应该也会感受到现在无论用的是半入耳&#xff0c;入耳式甚至是头戴式的耳机&#xff0c;都是会有一个问题&#xff1a;耳朵酸胀。因为我们无论是买一千的&#xff0c;还是几十的都是会痛&a…