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)) // 123
    printBoxValue(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) {}
    @NotNull
    public T load(@NotNull T x) {}
}

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

interface ArcadeGame<T1> : Game<T1> {
    override fun save(x: T1): T1
    // T1 is definitely non-nullable
    override 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 null
    return 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)
}

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

[STM32-1.点灯大师上线】

学习了江协科技的前4课&#xff0c;除了打开套件的第一秒是开心的&#xff0c;后面的时间都是在骂娘。因为51的基础已经几乎忘干净&#xff0c;c语言已经还给谭浩强&#xff0c;模电数电还有点底子&#xff0c;硬着头皮上吧。 本篇主要是讲述学习点灯的过程和疑惑解释。 1.工…

为什么每天都要做晨会?揭秘研发团队的默契背后

嗨&#xff0c;亲爱的小米粉丝们&#xff01;欢迎来到小米科技的微信公众号&#xff0c;我是小米&#xff0c;一个对技术充满热情、乐于分享的小伙伴。今天&#xff0c;我们要聊的话题是在研发过程中为什么每天都要进行晨会。 晨会是什么&#xff1f; 首先&#xff0c;我们得…

springboot的常用注解

声明解释这个对象&#xff08;类或者其他&#xff09;组件相关 名称作用Controller用于修饰MVC中controller层的组件SpringBoot中的组件扫描功能会识别到该注解&#xff0c;并为修饰的类实例化对象&#xff0c;通常与RequestMapping联用&#xff0c;当SpringMVC获取到请求时会…

用友NC word.docx接口存在任意文件读取漏洞

声明 本文仅用于技术交流&#xff0c;请勿用于非法用途 由于传播、利用此文所提供的信息而造成的任何直接或者间接的后果及损失&#xff0c;均由使用者本人负责&#xff0c;文章作者不为此承担任何责任。 一、产品介绍 用友 NC Cloud&#xff0c;大型企业数字化平台&#xff…

DC电源模块的常见故障有哪些?

BOSHIDA DC电源模块的常见故障有哪些&#xff1f; DC电源模块是电子设备中常见的电源供应模块&#xff0c;它可以将交流电转化为直流电供给设备使用。然而&#xff0c;由于长期的使用和外界环境等因素的影响&#xff0c;DC电源模块也会出现各种故障。下面我们来介绍一下常见的…

CentOS 7 虚拟机java项目部署tomcat

首先安装java环境 下载安装包:jdk-19_linux-x64_bin.tar.gz_免费高速下载|百度网盘-分享无限制 (baidu.com) 将安装包上传到虚拟机 解压 tar zxvf jdk-19_linux-x64_bin.tar.gz 移动文件到 mv jdk-19.0.1 /usr/jdk-19.0.1 编辑配置文件 vim /etc/profile export JAVA…

卷积神经网络中用1*1 卷积有什么作用或者好处呢?

一、来源&#xff1a;[1312.4400] Network In Network &#xff08;如果11卷积核接在普通的卷积层后面&#xff0c;配合激活函数&#xff0c;即可实现network in network的结构&#xff09; 二、应用&#xff1a;GoogleNet中的Inception、ResNet中的残差模块 三、作用&#x…

16、fixture的其它使用特点

官方实例 # content of test_use_more_fixture.py import pytest# Arrange pytest.fixture def first_entry():return "a"# Arrange pytest.fixture def second_entry():return 2# Arrange pytest.fixture def order(first_entry, second_entry):return [first_entr…

Ubuntu20.04安装向日葵、开机自启、解决windows系统远程黑屏(笔记)

这里写目录标题 动机1. Ubuntu20.04 安装向日葵2. 设置开机自启3. 解决windows不可远程的问题4. 大公告成 动机 办公室有个工作站&#xff0c;要比我的笔记本的CPU稍微好一点&#xff0c;用来跑陆面过程。我信心满满的装了个Ubuntu20.04双系统,但是发现向日葵安装不上了。我少…

基于CentOS7.9搭建LNMP

今天为大家搭建一下基于CentOS7.9服务器部署LNMP,方便未来基于lnmp部署应用程序。 1.CentOS7.9基础配置 为了实现基于CentOS7.9服务器部署LNMP,CentOS7.9服务器需要满足一定的基础需要。具体参考:Linux常规基础配置_linux操作系统配置_小黑要上天的博客-CSDN博客 2.服务器…

lv11 嵌入式开发 RTC 17

目录 1 RTC简介 ​编辑2 Exynos4412下的RTC控制器 2.1 概述 2.2 特征 2.3 功能框图 3 寄存器介绍 3.1 概述 3.2 BCD格式的年月日寄存器 3.3 INTP中断挂起寄存器 3.4 RTCCON控制寄存器 3.5 CURTICCNT 作为嘀嗒定时器使用的寄存器 4 RTC编程 5 练习 1 RTC简介 RTC(…

linux部署前端静态页面(实战)

Linux基本命令&#xff08;学习笔记&#xff09;零基础入门linux系统运维_linux find exec rm_Z_Xshan的博客-CSDN博客 如果linux不熟可以看我之前写的入门教程 感谢支持&#xff01;&#xff01; 一、服务器 这里去购买云服务器&#xff0c;如果是练习可以用虚拟机&#xff…

06、pytest将多个测试放在一个类中

官方用例 # content of test_class.py # 实例1 class TestClass:def test_one(self):x "this"assert "h" in xdef test_two(self):x "hello"assert hasattr(x,"check")# content of test_class_demo.py # 每个测试都有唯一的类实例…

《形式语言与自动机理论(第4版)》笔记(二)

文章目录 [toc]前导《形式语言与自动机理论&#xff08;第4版&#xff09;》笔记&#xff08;一&#xff09; 第三章&#xff1a;有穷状态自动机3.1|语言的识别3.2|有穷状态自动机即时描述 s e t ( ) set() set()例题问题 1 1 1解答问题 2 2 2解答 3.3|不确定的有穷状态自动机构…

机器学习——logistic回归

在之前实验接触到的机器学习算法都是的目的都是对数据集进行分类&#xff0c;即预测得到的结果是数据样本所属的类别&#xff0c;预测输出结果是离散的集合&#xff0c;比如{‘是’&#xff0c;‘不是’}。这次实验学习的是回归模型&#xff0c;与分类模型不同的是&#xff0c;…

邻接矩阵深度优先遍历

深度优先遍历&#xff08;Depth-First Search&#xff0c;DFS&#xff09;是一种用于遍历或搜索树或图的算法。这个算法会尽可能深地搜索树的分支。当节点v的所在边都己被探寻过&#xff0c;搜索将回溯到发现节点v的那条边的起始节点。 在用邻接矩阵表示的图中进行深度优先遍历…

第N个泰波那锲数

1.题目解析 动态规划算法原理 1.状态表示 2.状态转移方程 3.初始化 4.填表顺序 为了填写当前状态的时候&#xff0c;所需要的状态已经计算过了&#xff01;&#xff01;&#xff01; 5.返回值 题目要求 状态表示 ------------------------------------------------------…

单机游戏推荐:巨击大乱斗 GIGABASH 中文安装版

在泰坦之中称霸天下吧&#xff01;《GigaBash 巨击大乱斗》是一款多人战斗擂台游戏&#xff0c;有着受特摄片启发的巨型怪兽&#xff0c;具有传奇色彩的英雄&#xff0c;震天动地的特别攻击&#xff0c;以及可以完全摧毁的擂台场景。 ​游戏特点 怪物大解放 多达10个独特的角…

U2 文法和语言

文章目录 一、预备知识1、字母表和符号串2、符号串形式定义3、符号串相等4、延申 二、文法1、 一些概念2、文法的定义3、推导的形式定义4、规范推导5、语言的形式定义6、递归文法7、BNF范式总结 三、短语、简单短语和句柄四、语法树(推导树)1、概念2、语法树推导--句型的推导3、…

AI Agent 结构与分类

一、什么是AI agent 在人工智能中&#xff0c;智能代理AI Agent是以智能方式行事的代理&#xff1b;它感知环境&#xff0c;自主采取行动以实现目标&#xff0c;并可以通过学习或获取知识来提高其性能。人其实就是一种复杂代理。 为了理解智能代理的结构&#xff0c;我们应该熟…