Kotlin 官方学习教程之密封类与泛型

2023-05-16

密封类

密封类用于表示受限类层次结构,当值可以有一个有限集合的类型,但不能有其他类型。它们在某种意义上是枚举类的扩展:枚举类型的值集合也受到限制,但每个枚举常量仅作为单个实例存在,而密封类的子类可以包含多个可包含的实例州。

要声明一个密封的类,您需要将密封的修饰符放在类的名称之前。密封类可以具有子类,但它们都必须在与密封类本身相同的文件中声明。 (在 vKotlin 1.1 之前,规则更加严格:类必须嵌在密封类的声明内)。

sealed class Expr
data class Const(val number: Double) : Expr()
data class Sum(val e1: Expr, val e2: Expr) : Expr()
object NotANumber : Expr()

(上面的例子使用了Kotlin 1.1的一个额外的新功能:数据类扩展其他类的可能性,包括密封类。)

请注意,扩展封装类(间接继承器)子类的类可以放置在任何位置,而不一定在同一个文件中。

使用密封类的关键好处是在一个 when 表达式中使用它们。如果可以验证该语句涵盖所有情况,则不需要在语句中添加一个 else 子句。

fun eval(expr: Expr): Double = when(expr) {
    is Const -> expr.number
    is Sum -> eval(expr.e1) + eval(expr.e2)
    NotANumber -> Double.NaN
    //else”子句不是必需的,因为我们已经涵盖了所有的情况
}

泛型

和在 Java 中一样,Kotlin 中的类可以拥有类型参数:

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

一般来说,创建一个这样类的实例,我们需要提供类型参数:

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

但是如果类型是有可能推断出来的,比如来自构造函数的参数或者通过其它的一些方式,一个可以忽略类型的参数:

val box = Box(1) // 1 的类型为 Int,所以编译器可以推断出我们需要的类型为 Box <Int>

变型

Java 类型系统最棘手的一部分就是通配符类型。但 kotlin 没有,代替它的是两种其它的东西:声明变型和类型投影.

首先,我们想想为什么 java 需要这些神秘的通配符。这个问题在 Effective Java ,条目 18 中是这样解释的:使用界限通配符增加 API 的灵活性。首先 java 中的泛型是不变的,这就意味着 List 不是 List 的子类型。为什么呢,如果 List 不是不变的,就会引发下面的问题:

// Java
List<String> strs = new ArrayList<String>();
List<Object> objs = strs; // !!! 即将到来的问题的原因在这里。 Java 禁止这个!
objs.add(1); // 这里我们把一个整数放入一个字符串列表中
String s = strs.get(0); // !!! ClassCastException:不能将 Integer 转换为 String

因此 java 禁止了这样的事情来保证运行时安全。但这有些其它影响。比如,Collection 接口的 addAll() 方法。这个方法的签名在哪呢?直觉告诉我们应该是这样的:

// Java
interface Collection<E> ... {
  void addAll(Collection<E> items);
}

但接下来我们就不能做下面这些操作了(虽然这些操作都是安全的):

// Java
void copyAll(Collection<Object> to, Collection<String> from) {
  to.addAll(from); // !!! 不会用 addAll 的 naive 声明来编译:
                   //       Collection<String> 不是 Collection<Object> 的子类型
}

这就是为什么 addAll() 的签名是下面这样的:

// Java
interface Collection<E> ... {
  void addAll(Collection<? extends E> items);
}

这个通配符参数 ? extends T 意味着这个方法接受一些 T 类型的子类而非 T 类型本身。这就是说我们可以安全的读 T’s(这里表示 T 子类元素的集合),但不能写,因为我们不知道 T 的子类究竟是什么样的,针对这样的限制,我们很想要这样的行为:Collection 是 Collection

声明处变型

假设我们有一个通用接口 Source,没有任何接受 T 作为参数的方法,唯一的方法就是返回 T:

// Java
interface Source<T> {
  T nextT();
}

然后,储存 Source(String) 的一个实例引用给一个 Source(object) 类型是非常安全的,但是 Java 并不知道并且继续禁止这么做:

// Java
void demo(Source<String> strs) {
  Source<Object> objects = strs; // !!! 在 Java 中是不允许的
  // ...
}

为了解决这个问题,我们必须要声明一个 Source

abstract class Source<out T> {
    abstract fun nextT(): T
}

fun demo(strs: Source<String>) {
    val objects: Source<Any> = strs // This is OK, since T is an out-parameter
    // ...
}

一般原则是:当一个类 C 的类型参数被声明为 out 时,它就只能出现在 C 的成员的输出-位置,结果是 C 可以安全地作为 C的超类。

更聪明的说法就是,当类 C 在类型参数 T 之下是协变的,或者 T 是一个斜变类型。可以把 C 想象成 T 的生产这,而不是 T 的消费者。

out 修饰符本来被称之为变型注解,但由于同处与类型参数声明处,我们称之为声明处变型。这与 Java 中的使用处变型相反。

另外除了 out,Kotlin 又补充了一个变型注释:in。它接受一个类型参数逆变:只可以被消费而不可以 被生产。非变型类的一个很好的例子是 Comparable:

abstract class Comparable<in T> {
    abstract fun compareTo(other: T): Int
}

fun demo(x: Comparable<Number>) {
    x.compareTo(1.0) // 1.0 has type Double, which is a subtype of Number
    // Thus, we can assign x to a variable of type Comparable<Double>
    val y: Comparable<Double> = x // OK!
}

我们相信 in 和 out 两词是自解释的(因为它们已经在 C# 中成功使用很长时间了), 因此上面提到的助记符不是真正需要的,并且可以将其改写为更高的目标:

存在性(The Existential) 转变:消费者 in, 生产者 out! :-)

类型投影

使用处变型:类型投影

将类型参数 T 声明为 out 非常方便,并避免在使用站点上进行子类型的麻烦,但是某些类实际上不能仅限于返回 T!一个很好的例子是 Array:

class Array<T>(val size: Int) {
    fun get(index: Int): T { /* ... */ }
    fun set(index: Int, value: T) { /* ... */ }
}

这个类不能在 T 中共同或逆转换。这种强加了一些不灵活性。考虑以下函数:

fun copy(from: Array<Any>, to: Array<Any>) {
    assert(from.size == to.size)
    for (i in from.indices)
        to[i] = from[i]
}

该函数应该将项目从一个数组复制到另一个数组。我们试着在实践中应用它:

val ints: Array<Int> = arrayOf(1, 2, 3)
val any = Array<Any>(3) { "" } 
copy(ints, any) // Error: expects (Array<Any>, Array<Any>)

在这里我们遇到了同样的熟悉的问题:Array 在 T 中是不变的,因此 Array 和 Array 都不是另一个的子类型。为什么,再次,因为复制可能会做坏事,也就是说可能会尝试写一个字符串来,如果我们实际上传递了一个 Int 的数组,那么 ClassCastException 将在稍后抛出。

那么,我们唯一要确保的是 copy() 不会做任何坏事。我们要禁止它写信给我们,我们可以这样做:

fun copy(from: Array<out Any>, to: Array<Any>) {
 // ...
}

这里发生的事情就是类型投影:我们说,from 不仅仅是一个数组,而是一个有限的(预计的)数组:我们只能调用返回类型参数 T 的方法,在这种情况下,这意味着我们只能调用 get()。这是我们使用处变型:类型投影的方法,对应于 Java 的 Array <?extends Object>,但是稍微简单一些。

你也可以投入一个类型:

fun fill(dest: Array<in String>, value: String) {
    // ...
}

Array 对应 Java 的 Array

通用函数

不是只有类可以有类型参数。函数也可以。类型参数放在函数名称之前:

fun <T> singletonList(item: T): List<T> {
    // ...
}

fun <T> T.basicToString() : String {  // extension function
    // ...
}

要调用一个通用函数,在函数名称后面的调用处中指定类型参数:

val l = singletonList<Int>(1)

常见约束

可以替代给定类型参数的所有可能类型的集合可能受到通用约束的限制。

上限

最常见的约束类型是对应于 Java 的 extends 关键字的上限:

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

冒号后面指定的类型是上限:只有 Comparable的子类型可以被替换为 T 。例如

sort(listOf(1, 2, 3)) // OK. Int is a subtype of Comparable<Int>
sort(listOf(HashMap<Int, String>())) // 错误: HashMap<Int, String> 不是 Comparable<HashMap<Int, String>> 的子类型

默认上限(如果没有指定)为 Any? 在尖括号内只能指定一个上限。如果同一类型的参数需要多个上限,我们需要一个单独的 where 子句:

fun <T> cloneWhenGreater(list: List<T>, threshold: T): List<T>
    where T : Comparable,
          T : Cloneable {
  return list.filter { it > threshold }.map { it.clone() }
}
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Kotlin 官方学习教程之密封类与泛型 的相关文章

随机推荐

  • Tomcat 8.0 Mac 安装与配置

    工具 原料 1 xff0c JDK xff1a 版本为jdk 8u40 macosx x64 dmg 下载地址http www oracle com technetwork java javase downloads jdk8 downlo
  • 个人安卓学习笔记---java.io.IOException: Unable to open sync connection!

    在使用手机调试程序的时候出现了java io IOException Unable to open sync connection 这样的异常 xff0c 我尝试使用拔掉USB然后重新 xff0c 插入 xff0c 结果失败 再尝试 xff
  • "_OBJC_CLASS_$_Play", referenced from:

    IOS做了这么久也没写过什么博客 xff0c 不好不好 xff0c 今天开始写 遇到的问题 xff1a 34 OBJC CLASS Play 34 referenced from 解决方案 xff1a Tagert Build Phases
  • Blog--›Mac TNT 软件下载地址

    使用mac OS时 经常性的需要寻找tnt软件 你懂得 下载安装 由于国内网络的限制 百度搜索引擎的结果 基本都是国内搬运者的搬运结果 有些还需要vip才能下载 文章目录 原网址常用软件下载地址整理 2020 11 16 Adobe Pho
  • 树莓派SSH远程连接连接失败的解决办法

    树莓派SSH远程连接 将全新的树莓派系统烧录 xff0c 开机然后用SSH远程连接 xff0c 结果SSH连接提示 connection refused xff0c 导致连接树莓派失败 出现错误的原因是自 2016 11 25 官方发布的新
  • 在树莓派中安装ROS系统(Kinetic)

    在树莓派中安装ROS系统 重新梳理了一下树莓派的安装流程 xff0c 现在我们来开始吧 打开官网教程 http wiki ros org kinetic step1 安装源 xff08 中国 xff09 sudo sh c 39 etc l
  • ROS学习笔记-roscd指令

    对ROS文件系统而言 xff0c ROS中的roscd命令实现利用包的名字直接切换到相应的文件目录下 xff0c 命令使用方法如下 xff1a span class hljs tag roscd span span class hljs a
  • configure it with blueman-service

    用下面这个命令把Linux目录的名字由中文改成英文了 export LANG span class hljs subst 61 span en US xdg span class hljs attribute user span span
  • 关于Ubuntu16.04升级系统后启动报错问题的修复

    关于Ubuntu16 04升级系统后启动报错问题的修复 Ubuntu16 04升级后启动报错为 Failed to start Load Kernel Modules 使用systemctl status systemd modules l
  • Ubuntu Mate 自动登录

    树莓派安装Ubuntu Mate 设置自动启动 需要修改文件 usr share lightdm lightdm conf d 60 lightdm gtk greeter conf sudo vim usr share lightdm l
  • 记一次GL error: Out of memory!的崩溃

    现象描述 xff1a 设备外接UVC摄像头 xff0c 使用uvccamera库去打开 xff0c 在进行打开 gt 关闭压测的过程中 xff0c 发现到了940多次进程就崩溃 xff0c 大致log如下 xff1a 2020 05 04
  • Java中接口(Interface)的定义和使用

    有关 Java 中接口的使用相信程序员们都知道 xff0c 但是你们知不知道接口到底有什么用呢 xff1f 毫无疑问 xff0c 接口的重要性远比想象中重要 接下来我们便一起来学习Java中接口使用 Java接口是什么 Java接口是一系列
  • Java中向下转型的意义

    什么是向上转型和向下转型 在Java继承体系中 xff0c 认为基类 xff08 父类 超类 xff09 在上层 xff0c 导出类 xff08 子类 继承类 派生类 xff09 在下层 xff0c 因此向上转型的意思就是把子类对象转成父类
  • Java中单例模式的使用

    什么是单例模式 单例模式 xff0c 也叫单子模式 xff0c 是一种常用的软件设计模式 在应用这个模式时 xff0c 单例对象的类必须保证只有一个实例存在 许多时候整个系统只需要拥有一个的全局对象 xff0c 这样有利于我们协调系统整体的
  • acc--›Android无障碍开发入门

    文章目录 前言创建无障碍程序1 配置无障碍信息属性的说明accessibilityEventTypesaccessibilityFeedbackTypeaccessibilityFlagscanControlMagnification 96
  • Android RecyclerView完全解析

    什么是RecyclerView xff1f RecyclerView 是谷歌 V7 包下新增的控件 用来替代 ListView 的使用 在 RecyclerView 标准化了 ViewHolder 类似于 ListView 中 conver
  • 程序员也是会浪漫的->打造浪漫的Android表白程序

    一年前 xff0c 看到过有个牛人用HTML5绘制了浪漫的爱心表白动画 xff0c 后来又在华超的这篇文章上看到大神用Android写出了相同的效果 xff0c 于是也动手写了一下 xff0c 并加了一些功能 xff0c 感谢大神的指引 写
  • Android登录注册功能封装

    我们都知道Android应用软件基本上都会用到登录注册功能 xff0c 那么对一个一个好的登录注册模块进行封装就势在必行了 这里给大家介绍一下我的第一个项目中所用到的登录注册功能的 xff0c 已经对其进行封装 xff0c 希望能对大家有帮
  • Kotlin 官方学习教程之扩展

    扩展 类似于 C 和 Gosu xff0c Kotlin 也提供了一种可以在不继承父类也不使用类似装饰器这样的设计模式的情况下对指定类进行扩展的功能 这是通过称为扩展名的特殊声明来实现的 Kotlin 支持函数扩展和属性扩展 函数扩展 要声
  • Kotlin 官方学习教程之密封类与泛型

    密封类 密封类用于表示受限类层次结构 xff0c 当值可以有一个有限集合的类型 xff0c 但不能有其他类型 它们在某种意义上是枚举类的扩展 xff1a 枚举类型的值集合也受到限制 xff0c 但每个枚举常量仅作为单个实例存在 xff0c