【Kotlin精简】第7章 泛型

1 泛型

泛型即 “参数化类型”,将类型参数化,可以用在接口函数上。与 Java 一样,Kotlin 也提供泛型,为类型安全提供保证,消除类型强转的烦恼。

在这里插入图片描述

1.1 泛型优点

  1. 类型安全:通用允许仅保留单一类型的对象。泛型不允许存储其他对象。
  2. 不需要类型转换:不需要对对象进行类型转换。
  3. 编译时检查:在编译时检查泛型代码,以便在运行时避免任何问题。

1.2 泛型声明

1.2.1 泛型类

interface List<T> {
	fun get(index: Int): T
}

泛型参数可在类中当普通类型使用。

1.2.2 泛型函数

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

在 fun 关键字后声明 泛型形参,可在参数和返回值处声明使用。高阶函数的例子:

fun <T> List<T>.filter(pridicate: (T) -> Boolean): List<T> { ... }

1.2.3 泛型属性

val <T> List<T>.last: T
      get() {
          return last()
      }

不管是泛型类泛型函数还是泛型属性,在使用之前必然已经确定了类型。如泛型类在实例化时需要指定泛型类型,泛型函数在调用时必然已推导出泛型类型,并替换为确定的类型实参,泛型属性同理。

1.3 泛型约束

泛型(类型参数)是有边界的,可以给泛型设置边界:

interface NumberList<T: Number> {
        fun get(index: Int): T
}

不指定边界,则默认上边界为 Any?。如果希望非空,需要显示指定为 <T: Any>

在这里插入图片描述

2 泛型擦除

Java 一样,Kotlin 中的类型参数也会在运行时被擦除,就是说泛型实例的类型实参在运行时是不保留的。不过 Kotlin 可以通过类型参数实化的方式保留类型信息,需要使用内联函数。

由于泛型擦除,下面的普通方法是无法编译的:

fun <T> isA(obj: Any): Boolean {
	return obj is T
}

通过内联,下面代码可以通过编译:

inline fun <reified T> isA(obj: Any): Boolean {
	return obj is T
}

注意带 reified 类型参数的内联函数不能在 Java 代码中使用,普通内联函数在 Java 中可以像常规函数一样调用,而 reified 的类型参数需要额外处理将类型实参替换到字节码,是永远需要内联的。
实化类型参数也是有限制的,具体可以做:

  1. 类型转换和检查: 如 is 、as
  2. 使用反射: T::class
  3. 获取 java class: T::class.java
  4. 作为调用其他函数类型的实参。

3 变型

变型描述的是具有相同基础类型不同类型参数的泛型类型之间的关系。这种关系可以是 协变的逆变的

先说说不变型,一个泛型类如 MutableList ,对任意两种类型实参 ABMutableList<A> 既不是 MutableList<B> 的子类型也不是它的超类型,则称 该类在该类型参数上是不变型的。Java 中的泛型类对所有类型参数都是不变型的。比如 Java 中你不能把一个 List<Ingeter> 实例传给形参是 List<Number> 的函数,即使 IntegerNumber 的子类。

前面说的子父类型关系类似于类的子父类关系,比如 AB 的子类,那么 AB 的子类型,任一非空类型是其可空类型的子类型,比如 PersonPerson? 的子类型,下面会说到 Kotlin 中借助协变使得 List<Int> 能够成为 List<Number> 的子类型,注意区分 子类子类型

Kotlin 中,比如上面自定义的 List 接口,也是不变型的,List<Int> 并不是 List<Number> 的子类型,因此你不能把一个 List<Int> 实例传给形参是 List<Number> 的函数。

注意 Kotlin 标准库中的 List 接口是可以的,因为是协变的,别和这里自定义的 List 搞混了

3.1 Out (协变)

对于 out 泛型,我们能够将使用子类泛型的对象赋值给使用父类泛型的对象。如果将上面的 List 接口定义改为:

interface List<out T> {
	fun get(index: Int): T
}

则称该 List 接口是协变的,如果基础类型间有子类型关系,则泛型类也具有相同的子类型关系。如 IntNumber 的子类型,则 上面定义的 List<Int> 也是List<Number> 的子类型。这样就可以将 一个 List<Int> 实例传给形参是 List<Number> 的函数了。

当然,out 也不可以滥用 ,因为不安全,比如将一个 List<Int> 实例传入形参是 List<Any> 的函数,该函数像实例中添加 Any 类型的数据显然是错误的:

fun addMore(list: List<Any>) {
	list.add("abc")
}
addMore(listOf(1, 2, 3))

为了防止这种风险,如果类在该类型参数上是协变的,那么该类型参数只能出现在返回值位置,我们称之为 out 位置,即该泛型参数只读,编译器也会做这种检查。Kotlin 中的 List 接口就是协变的。

3.2 In (逆变)

和协变相反,对于 in 泛型,我们可以将使用父类泛型的对象赋值给使用子类泛型的对象。如果一个泛型类 MyClass 是逆变的,则对于 有子类型关系的 A B(A是B的子类型),则 MyClass<A>MyClass<B> 的超类型。

在这里插入图片描述

例如 Comparable 接口:

public interface Comparable<in T> {
	public operator fun compareTo(other: T): Int
}

那么,Comparable<Any>Comparable<Int> 的子类型。可以尝试理解成“能对 Any 类型进行比较”的比较器也能比较 Int 类型“。

类似的,这里的泛型参数只能出现在函数参数位置,我们称之为 in 位置。

3.3 声明点变型

即声明泛型的地方产生变型。前面说的变型是针对类的所有实例的。而声明点变型则可以只针对某一实例变型。如:

val list: MutableList<out Int> = MutableList()
list.add(1,2) //报错

上面代码会将 list 变为只读的。

Java 中,没有类的变型声明,只通过声明点产生型变,如:

public interface Stream<T> {
	 <R> Stream<R> map(Function<? super T, ? extends R> mapper);
}

Kotlin 不同的是,Java 通过 ? super T 产生逆变? extends R 产生协变

3.4 星号投影

List<*> 对应与 Java 中的 List<?>, 表示不确定的任意类型类型实参。可能是 Int,可能是Any,是确定的某种类型,但对使用者是未知的,因而不能生产该值,只能访问,当作 Any? 访问。注意和 List<Any?> 作区分。

4 小结

  1. Kotlin 的泛型概念和声明和 Java 相当接近。
  2. Kotlin 的类型实参和 Java 一样会在运行期擦除。
  3. Kotlin 可以通过类型参数实化保留运行时类型实参,需要借助内联函数。
  4. 变型指的是具有相同基础类型和不同类型参数的泛型类型间的子类型关系。他指出了如果一个泛型类型的类型参数是另一个泛型类型类型参数的子类型,那么这个泛型类就是另一个泛型类的子类型或超类型。
  5. 如果某个类在一个类型参数上声明成协变的,那么该类型参数只能出现在 out 位置上。逆变相反。Java 的泛型类都是不变型的。
  6. 声明点泛型只在声明处产生变型,Java 的变型都是该方式。Kotlin 既可以使用声明点变型,也可以在整个泛型类上声明变型。
  7. 如果确切的类型实参是未知的或不重要的时候,可以使用星号投影

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

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

相关文章

CoDeSys系列-4、基于Ubuntu的codesys运行时扩展包搭建Profinet主从环境

CoDeSys系列-4、基于Ubuntu的codesys运行时扩展包搭建Profinet主从环境 文章目录 CoDeSys系列-4、基于Ubuntu的codesys运行时扩展包搭建Profinet主从环境一、前言二、资料收集三、Ubuntu18.04从安装到更换实时内核1、下载安装Ubuntu18.042、下载安装实时内核&#xff0c;解决编…

如何将PDF文件转换成翻页电子书?这个网站告诉你

​随着电子书的普及&#xff0c;越来越多的人开始将PDF文件转换成翻页电子书。翻页电子书不仅方便阅读&#xff0c;而且还可以在手机上轻松翻页。那么如何将PDF文件转换成翻页电子书呢&#xff1f;今天就为大家介绍一个网站&#xff0c;可以帮助你轻松完成这个任务。 1.首先&am…

Proteus仿真--12864LCD显示计算器键盘按键实验(仿真文件+程序)

本文主要介绍基于51单片机的12864LCD液晶显示电话拨号键盘按键实验&#xff08;完整仿真源文件及代码见文末链接&#xff09; 仿真图如下 本设计主要介绍计算器键盘仿真&#xff0c;按键按下后在12864液晶上显示对应按键键值 仿真运行视频 Proteus仿真--12864LCD显示计算器…

【漏洞复现】IIS_7.o7.5解析漏洞

感谢互联网提供分享知识与智慧&#xff0c;在法治的社会里&#xff0c;请遵守有关法律法规 文章目录 1.1、漏洞描述1.2、漏洞等级1.3、影响版本1.4、漏洞复现1、基础环境2、漏洞扫描3、漏洞验证 1.5、修复建议 1.1、漏洞描述 漏洞原理&#xff1a; cgi.fix_path1 1.png/.php该…

第九章《搞懂算法:决策树是怎么回事》笔记

决策树算法是机器学习中很经典的一个算法&#xff0c;它既可以作为分类算法&#xff0c;也可以作为回归算法。 9.1 典型的决策树是什么样的 决策树算法是依据“分而治之”的思想&#xff0c;每次根据某属性的值对样本进行分类&#xff0c;然后传递给下个属性继续进行分类判断…

项目实战:新增@Controller和@Service@Repository@Autowire四个注解

1、Controller package com.csdn.mymvc.annotation; import java.lang.annotation.*; Target(ElementType.TYPE) Retention(RetentionPolicy.RUNTIME) Inherited public interface Controller { }2、Service package com.csdn.mymvc.annotation; import java.lang.annotation.*…

zookeeper节点类型

节点类型 持久节点&#xff08;Persistent Nodes&#xff09; 这些是Zookeeper中最常见的一种节点类型&#xff0c;当创建一个持久类型节点时&#xff0c;该值会一直存在zookeeper中&#xff0c;直到被显式删除或被新值覆盖。 临时节点&#xff08;Ephemeral Nodes&#xff…

【漏洞复现】Apache_Tomcat_PUT方法任意写文件(CVE-2017-12615)

感谢互联网提供分享知识与智慧&#xff0c;在法治的社会里&#xff0c;请遵守有关法律法规 文章目录 1.1、漏洞描述1.2、漏洞等级1.3、影响版本1.4、漏洞复现1、基础环境2、漏洞扫描3、漏洞验证工具扫描验证POC 1.6、修复建议 说明内容漏洞编号CVE-2017-12615漏洞名称Tomcat_PU…

【python】路径管理+路径拼接问题

路径管理 问题相对路径问题绝对路径问题 解决os库pathlib库最终解决 问题 环境&#xff1a;python3.7.16 win10 相对路径问题 因为python的执行特殊性&#xff0c;使用相对路径时&#xff0c;在不同路径下用python指令会有不同的索引效果&#xff08;python的项目根目录根据执…

服务器搭建:从零开始创建自己的Spring Boot应用【含登录、注册功能】

当然&#xff0c;你可以先按照IDEA搭建SSM框架【配置类、新手向】完成基础框架的搭建 步骤 1&#xff1a;设计并实现服务器端的用户数据库 在这个示例中&#xff0c;我们将使用MySQL数据库。首先&#xff0c;你需要安装MySQL并创建一个数据库以存储用户信息。以下是一些基本步…

5.3有效的括号(LC20-E)

算法&#xff1a; 题目中&#xff1a;左括号必须以正确的顺序闭合。意思是&#xff0c;最后出现的左括号&#xff08;对应着栈中的最后一个元素&#xff09;&#xff0c;应该先找到对应的闭合符号&#xff08;右括号&#xff09; 比如:s"( [ ) ]"就是False&#xf…

【错误解决方案】ModuleNotFoundError: No module named ‘my_fake_useragent‘

1. 错误提示 ModuleNotFoundError: No module named my_fake_useragent&#xff0c;这意味着你试图导入一个名为 my_fake_useragent 的模块&#xff0c;但Python找不到这个模块。 2. 解决方案 检查模块名是否正确: 确保你试图导入的模块名是正确的。也许你拼写错误或者大小写不…

【Midjourney入门教程1】Midjourney的注册、订阅

文章目录 前言一、Midjourney是什么二、Midjourney注册三、新建自己的服务器四、开通订阅 前言 AI绘画即指人工智能绘画&#xff0c;是一种计算机生成绘画的方式。是AIGC应用领域内的一大分支。 AI绘画主要分为两个部分&#xff0c;一个是对图像的分析与判断&#xff0c;即“…

onnx 模型加载部署运行方式

1.通过文件路径的onnx模型加载方式: 在onnxruntime下面的主要函数:session Ort::Session(env, w_modelPath.c_str(), sessionOptions); 这里的文件路径是宽字节的&#xff0c;通过onnx文件路径直接加载模型。 在opencv下使用dnn加载onnx模型的主要函数: std::string model…

Redo Log(重做日志)的刷盘策略

1. 概述 Redo Log&#xff08;重做日志&#xff09;是 InnoDB 存储引擎中的一种关键组件&#xff0c;用于保障数据库事务的持久性和崩溃恢复。InnoDB 将事务所做的更改先记录到重做日志&#xff0c;之后再将其应用到磁盘上的数据页。 刷盘策略&#xff08;Flush Policy&#x…

css基础之实现轮播图

原理介绍 图片轮播的原理是通过控制显示和隐藏不同的图片来实现图像的切换&#xff0c;从而创建连续播放的效果。用到的知识点有定位和定时器。 实现步骤&#xff1a; HTML 结构&#xff1a; 首先&#xff0c;需要在HTML中创建一个包含轮播图片的容器&#xff0c;通常使用 &l…

Golang源码分析之golang/sync之singleflight

1.1. 项目介绍 golang/sync库拓展了官方自带的sync库&#xff0c;提供了errgroup、semaphore、singleflight及syncmap四个包&#xff0c;本次分析singlefliht的源代码。 singlefliht用于解决单机协程并发调用下的重复调用问题&#xff0c;常与缓存一起使用&#xff0c;避免缓存…

要做一名成功的测试,首先得会想?

近在做测试时&#xff0c;突然想到了这么个问题——在测试的过程中对某个功能想得越开&#xff0c;测试就完整&#xff0c;就越彻底&#xff01; 当然我们在产生与该功能相关的想象时&#xff0c;其中最关键的是不能脱离需求&#xff0c;不能脱离该软件本身&#xff1b;不然这…

WebSocket Day02 : 握手连接

前言 握手连接是WebSocket建立通信的第一步&#xff0c;通过客户端和服务器之间的一系列握手操作&#xff0c;确保了双方都支持WebSocket协议&#xff0c;并达成一致的通信参数。握手连接的过程包括客户端发起握手请求、服务器响应握手请求以及双方完成握手连接。完成握手连接后…

优雅的 Dockerfile 是怎样炼成的?

Docker 简介 目前&#xff0c;Docker 主要有两个形态&#xff1a;Docker Desktop 和 Docker Engine。 Docker Desktop 是专门针对个人使用而设计的&#xff0c;支持 Mac&#xff08;已支持arm架构的M系芯片&#xff09; 和 Windows 快速安装&#xff0c;具有直观的图形界面&a…