【Kotlin精简】第7章 泛型

news2024/11/24 14:25:55

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. 如果确切的类型实参是未知的或不重要的时候,可以使用星号投影

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

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

相关文章

分享68个工作总结PPT,总有一款适合您

分享68个工作总结PPT&#xff0c;总有一款适合您 PPT下载链接&#xff1a;https://pan.baidu.com/s/1juus0gmesBFxJ-5KZgSMdQ?pwd8888 提取码&#xff1a;8888 Python采集代码下载链接&#xff1a;采集代码.zip - 蓝奏云 学习知识费力气&#xff0c;收集整理更不易。知识付…

【数据结构】数组和字符串(十五):字符串匹配2:KMP算法(Knuth-Morris-Pratt)

文章目录 4.3 字符串4.3.1 字符串的定义与存储4.3.2 字符串的基本操作4.3.3 模式匹配算法0. 朴素模式匹配算法1. ADL语言2. KMP算法分析3. 手动求失败函数定义例1例2例3 4. 自动求失败函数&#xff08;C语言&#xff09;5. KMP算法&#xff08;C语言&#xff09;6. 失败函数答案…

思维模型 凡勃伦效应

本系列文章 主要是 分享 思维模型&#xff0c;涉及各个领域&#xff0c;重在提升认知。为什么有些人愿意为高价商品买单&#xff1f;请看凡勃伦效应。 1 凡勃伦效应的应用 1.1 奢侈品市场中的凡勃伦效应 茅台酒&#xff1a;茅台酒是中国的一种高档白酒&#xff0c;价格非常昂…

在本地安装LLAMA 2

方法一&#xff1a; Meta已将llama2开源&#xff0c;任何人都可以通过在meta ai上申请并接受许可证、提供电子邮件地址来获取模型。 Meta 将在电子邮件中发送下载链接。 下载llama2 获取download.sh文件&#xff0c;将其存储在mac上打开mac终端&#xff0c;执行 chmod x ./do…

namespace

1.namespace技术 namespace是Linux内核的一组特性&#xff0c;支持对内核资源进行分区隔离&#xff0c;让一组进程只能看到一组资源&#xff0c;而另一组进程只能看到另一组不同的资源。换句话说&#xff0c;namespace的关键特性是进程隔离。在运行许多不同服务的服务器上&…

计网【链路带宽100Mbps代表什么,“翻译”成人话是?】

这里写目录标题 带宽的概念本来的意思【通信领域】计网中的意思 结论【100Mbps代表什么】 带宽的概念 本来的意思【通信领域】 带宽这个概念本来是通信领域的&#xff0c;表示通信线路允许通过的信号频带范围&#xff0c;单位是赫兹Hz 感觉最简单的意思&#xff0c;例如如果…

Javascript知识点详解:数组、Array 对象

目录 数组 定义 数组的本质 对象有两种读取成员的方法&#xff1a; length 属性 in 运算符 for...in 循环和数组的遍历 数组的空位 类似数组的对象 Array 构造函数 静态方法 Array.isArray() 实例方法 valueOf()&#xff0c;toString() push()&#xff0c;pop(…

ruby、Python 以及 Swift 语言关于 “Finally” 实现的趣谈

0. 概览 结构化代码语义是任何语言入门之必备基本功&#xff0c;想写出“意大利面条”似的美味代码么&#xff1f;直接干就对了&#xff01; 虽然上面有些“话糙理不糙”&#xff0c;但不可否认的是现今几乎所有高级语言都对代码结构化语义提供了良好的支持。入门码农们的第一…

循环队列练习

循环队列练习 相关内容&#xff1a; 1.队列顺序存储的不足 2.循环队列&#xff08;队列头尾相接的顺序存储结构&#xff09; //队列的初始化、入队、出队、取对头、计算队长度 #include<stdio.h> #define MAXSIZE 10 typedef int Status; #define OK 1 #define ERROR 0…

虚幻C+++基础 day2

角色移动与视角控制 Character类与相关API 创建Character子类MainPlayer.h // Fill out your copyright notice in the Description page of Project Settings.#pragma once#include "CoreMinimal.h" #include "GameFramework/Character.h" #include &q…

centos7.0+最快速安装docker的方法

先安装yum工具&#xff0c;然后添加阿里云的docker仓库&#xff0c;然后yum安装&#xff0c;然后启动 安装yum-config yum install yum-utils -y yum-config-manager --add-repo http://mirrors.aliyun.com/docker-ce/linux/centos/docker-ce.repo yum install docker-ce do…

一款简单而强大的文档翻译网站

一款文字/文件翻译的网站,支持多个领域的翻译&#xff0c;支持常见的语言翻译(韩/日/法/英/俄/德…),最大百分比的保持原文排版(及个别除外基本100%还原)。 新用户注册就有100页的免费额度&#xff0c;每月系统还会随机赠送翻译额度&#xff0c;说实话这比好多的企业要好的多了…

为什么没有面试机会?是因为你没有掌握这套完整的性能测试流程,

一、准备工作 在什么阶段开展性能测试工作&#xff1f;一般情况下&#xff0c;是在被测系统已完成功能测试、系统趋于稳定的情况下&#xff0c;才会进行性能测试。 1. 组建测试团队 根据被测系统的实际情况&#xff0c;组建一个性能测试团队&#xff0c;团队成员包括&#xff…

CUMT-----Java课后第五章编程作业

文章目录 一、题11.1 问题描述1.2 代码块1.3 运行截图 二、题22.1 问题描述2.2 代码块2.3 运行截图 一、题1 1.1 问题描述 (1)使用继承编写人类、教师、学生类的实体类。(2)编写测试类&#xff0c;实例化教师和学生类对象并显示。 1.2 代码块 public class Human {private S…

镭神智能C16的ROS驱动的安装方法

原文链接 前言 激光雷达赶上了自动驾驶了浪潮&#xff0c;国产激光雷达也越来越多。 最近团队要购买激光雷达&#xff0c;正好拿镭神智能的产品测试一下&#xff0c;安装驱动是首先要做的&#xff0c;因此在这里记录一下。 产品说明&#xff1a;http://www.leishen-lidar.com…

multiple kernel learning(MKL)多核学习

历史上之所以会出现多核学习&#xff08;MKL&#xff09;这个词&#xff0c;是因为在深度学习流行起来以前&#xff0c;kernel是处理非线性的默认方法&#xff0c;那个年代优化一个非线性函数不容易&#xff0c;每加一层复杂性可能就需要多设计一个优化算法&#xff0c;MKL就是…

使用 ChatGPT 提升 LeetCode 刷题效率

文章目录 1 背景2 操作步骤 1 背景 在做 LeetCode 的 SQL 题库时, 想在本地调试, 需要在本地的数据库上创建表以及准备测试数据, 大家都是有经验的开发人员, 简单粗暴的办法就不讲了 可以借助 ChatGPT 的能力, 生产数据库的表以及测试数据的 sql, 提升刷题效率 2 操作步骤 将…

K8S知识点(四)

&#xff08;1&#xff09;环境搭建-集群安装 查看所需镜像 定义下载镜像 循环下载镜像&#xff1a; 下载完成之后&#xff1a;查看一下镜像&#xff0c;名字也已经改成了k8s的名字 集群初始化只在master节点上运行&#xff0c; 出现sucessfully表示成功&#xff0c;提示要运…

5.3 连接和分离线程

方法 pthread_join(thread, status) pthread_detach(thread) pthread_attr_setdetachstate(attr, detachstate) pthread_attr_getdetachstate(attr) 连接 连接&#xff08;joining&#xff09;是一种线程之间完成同步的方法&#xff0c;举例如下。 pthread_join()方法会阻…

Ultra:知识图谱推理的基础模型

一、说明 训练单个通用模型来解决任意数据集始终是 ML 研究人员的梦想&#xff0c;尤其是在基础模型时代。虽然这些梦想已经在图像或自然语言等感知领域实现了&#xff0c;但它们是否可以在推理领域&#xff08;如图形&#xff09;中再现仍然是一个开放的挑战。 图片由作者根据…