解析Kotlin中的Nothing【笔记摘要】
1.Nothing的本质
Nothing 的源码很简单:
public class Nothing private constructor()
可以看到它是个class,但它的构造函数是 private 的,这就导致我们没法创建它的实例,并且在源码里 Kotlin 也没有帮我们创建它的实例。
基于这样的前提,当我们写出这个函数声明的时候:
fun nothing(): Nothing {}
我们不可能找到一个合适的值来返回,因为压根找不到Nothing的实例
2.作用一:作为函数「永不返回」的提示
上面的例子看起来是个悖论,但它其实就是 Nothing 存在的意义:它找不到任何可用的值,所以,以Nothing为返回值类型的函数一定是个不会返回的函数。
比如它可以总是抛异常:
fun nothing() : Nothing {throw RuntimeException("Nothing!")
}
这个写法并没有返回任何结果,而是抛异常了,所以是合法的。
注意:抛异常是可以忽略返回值,而且这不是 Nothing 的特性,在Java和Kotlin中都是如此:
//Java
public String getName() {throw new NullPointerException("不能为空!");
}//Kotlin
fun getName() : String {throw NullPointerException("不能为空!")
}
上面这个getName()看起来有点奇怪,为什么在一个函数中就只是抛出异常呢,但它的写法本身是完全合法的。如果把函数的名字改一下,再加个注释:
/**当遇到姓名为空的时候,请调用这个函数来抛异常
*/
fun throwOnNameNull() : String {throw NullPointerException("姓名不能为空!")
}
这就很合理了吧?不干别的,就只是抛异常。这是一种很常用的工具函数的写法,包括 Kotlin 和 Compose 的官方源码里也有这种东西。
那么我们继续来看它的返回值类型:我都不返回了,就没必要还写 String 了吧?那写什么?可以把它改成 Unit,
fun throwOnNameNull() : Unit {throw NullPointerException("姓名不能为空!")
}
甚至可以把它改成不写返回值。但这里不写返回值,实际上返回类型是Unit,Kotlin会自动帮我们加上,可以看这篇文章:解析Kotlin中的Unit【笔记摘要】
fun throwOnNameNull() : Unit {throw NullPointerException("姓名不能为空!")
}fun throwOnNameNull() {throw NullPointerException("姓名不能为空!")
}
不过,Kotlin 又进了一步,提供了一个额外的选项:你还可以把它改成 Nothing:
/**当任何变量为空的时候,请统一调用这个函数来抛异常
*/
fun throwOnNameNull() : Nothing {throw NullPointerException("姓名不能为空!")
}
虽然我找不到 Nothing 的实例,但是这个函数本来就是永远抛异常的,找不到实例也没关系。
这么写的价值就在于,Nothing 这个返回值类型能够给使用它的开发者一个明确的提示:这是个永远不会返回的函数。这种提示本身,就会给开发提供一些方便,它能很好地避免函数的调用者对函数的误解而导致的一些问题。
Kotlin 的源码、Compose 的源码里都有不少这样的实践,比如 Compose 的 noLocalProviderFor() 函数:
private fun noLocalProvidedFor(name: String): Nothing {error("CompositionLocal $name not present")
}
拓展:其实 Nothing 的永不返回特性,除了抛异常之外,还有一种场景,就是无限循环:
fun foreverRepeat(): Nothing {while (true) {...}
}
不过一般很少有人这么去用,大部分都是用在抛异常的场景
3.作用二:作为泛型对象的临时空白填充
另外 Nothing 除了「没有可用的实例」之外,还有个特性:它是所有类型共同的子类型。
不过,这个特性又有什么作用呢?它就能让你对于任何变量的赋值,都可以在等号右边写一个 Nothing
val nothing: Nothing = ???
var apple: Apple = nothing
这儿其实有个问题:前面刚说了 Nothing 不会有任何的实例,对吧?那么这个右边就算能填 Nothing 类型的对象,可是这个对象我用谁啊?谁也没法用。
但是如果不直接用 Nothing,而是把它作为泛型类型的实例化参数。一个元素类型为Nothing 的 List,将会导致我无法找到任何的元素实例来填充进去,但是这个 emptyList 本身是可以被创建的:
val emptyList: List<Nothing> = listOf()
var apples: List<Apple> = emptyList
结合上我们刚说的「Nothing 是所有类型的子类型」这个特性,我们是不是可以把这个空的 List 赋值给任何的 List 变量?(这里实际上用到了协变的知识)
val emptyList: List<Nothing> = listOf()
var apples: List<Apple> = emptyList
var users: List<User> = emptyList
var phones: List<Phone> = emptyList
var images: List<Image> = emptyList
这样,是不是就提供了一个通用的空 List 出来,让这一个对象可以用于所有 List 的初始化。有什么好处?既省事,又省内存,这就是好处。
这种用法不只可以用在 List,Set 和 Map 也都没问题:
val emptySet: Set<Nothing> = setOf()
var apples: Set<Apple> = emptySet
var users: Set<User> = emptySet
var phones: Set<Phone> = emptySet
var images: Set<Image> = emptySet
val emptyMap: Map<String, Nothing> = emptyMap()
var apples: Map<String, Apple> = emptyMap
var users: Map<String, User> = emptyMap
var phones: Map<String, Phone> = emptyMap
var images: Map<String, Image> = emptyMap
而且也不限于集合类型,只要是泛型都可以,你自定义的也行:
val emptyProducer: Producer<Nothing> = Producer()
var appleProducer: Producer<Apple> = emptyProducer
var userProducer: Producer<User> = emptyProducer
var phoneProducer: Producer<Phone> = emptyProducer
var imageProducer: Producer<Image> = emptyProducer
它的核心在于,你利用 Nothing 可以创建出一个通用的「空白」对象,它什么实质内容也没有,什么实质工作也做不了,但可以用来作为泛型变量的一个通用的空白占位值。
4.作用三:语法的完整化
另外,Nothing 的「是所有类型的子类型」这个特点,还帮助了 Kotlin 语法的完整化。
在 Kotlin 的下层逻辑里,throw 这个关键字是有返回值的,它的返回值类型就是 Nothing。虽然说由于抛异常这件事已经跳出了程序的正常逻辑,所以 throw 返回不返回值、返回值类型是不是 Nothing 对于它本身都不重要,但它让这种写法成为了合法的:
val nothing: Nothing = throw RuntimeException("抛异常!")
并且因为 Nothing 是所有类型的子类型,所以我们这么写也行:
val nothing: String = throw RuntimeException("抛异常!")
看起来没用是吧?如果我再把它改改,就有用了:
var _name: String? = null
val name: String = _name ?: throw NullPointerException("_name 在运行时不能为空!")
throw 的返回值是 Nothing,我们就可以把它写在等号的右边,在语法层面假装成一个值来使用,但其实目的是在例外情况时抛异常。虽然 throw 不会真正地返回,但这让语法层面变得完全说得通了,这也是 Nothing 的价值所在。
除了 throw 之外,单独的 return 也是被规定为返回 Nothing 的一个关键字,所以我也可以这么写:
fun sayMyName(first: String, second: String) {val name = if (first == "Walter" && second == "White") {"Heisenberg"} else {return // 语法层面的返回值类型为 Nothing,赋值给 name}println(name)
}
单独 return 语法层面的返回值类型为 Nothing,可以赋值给 name,让代码从语法层面就能得到解释,从而帮助了 Kotlin 语法的完整化。
参考文章:
这玩意真的有用吗?对,是的!Kotlin 的 Nothing 详解
相关文章:
解析Kotlin中的Nothing【笔记摘要】
1.Nothing的本质 Nothing 的源码很简单: public class Nothing private constructor()可以看到它是个class,但它的构造函数是 private 的,这就导致我们没法创建它的实例,并且在源码里 Kotlin 也没有帮我们创建它的实例。 基于这…...
toRefs 和 toRef
文章目录 toRefs 和 toReftoRefstoRef toRefs 和 toRef toRefs toRefs 把一个由reactive对象的值变为一个一个ref的响应式的值 import { ref, reactive, toRefs, toRef } from vue; let person reactive({name: 张三,age: 18, }); // toRefs 把一个由reactive对象的值变为一…...
Vision Transformer论文阅读笔记
目录 An Image is Worth 16x16 Words: Transformers for Image Recognition at Scale -- Vision Transformer摘要Introduction—简介RELATED WORK—相关工作METHOD—方法VISION TRANSFORMER (VIT)—视觉Transformer(ViT) 分析与评估PRE-TRAINING DATA REQUIREMENTS—预训练数据…...
MapReduce的执行流程排序
MapReduce 是一种用于处理大规模数据集的分布式计算模型。它将作业分成多个阶段,以并行处理和分布式存储的方式来提高计算效率。以下是 MapReduce 的执行流程以及各个阶段的详细解释: 1. 作业提交(Job Submission) 用户通过客户端…...
雅思词汇及发音积累 2024.7.3
银行 check (美)支票 cheque /tʃek/ (英)支票 ATM 自动取款机 cashier 收银员 teller /ˈtelə(r)/ (银行)出纳员 loan 贷款 draw/withdraw money 提款 pin number/passsword/code …...
Vue2和Vue3的区别Vue3的组合式API
一、Vue2和Vue3的区别 1、创建方式的不同: (1)、vue2:是一个构造函数,通过该构造函数创建一个Vue实例 new Vue({})(2)、Vue3:是一个对象。并通过该对象的createApp()方法,创建一个vue实例。 Vue…...
ML307R OpenCPU HTTP使用
一、函数介绍 二、示例代码 三、代码下载地址 一、函数介绍 具体函数可以参考cm_http.h文件,这里给出几个我用到的函数 1、创建客户端实例 /*** @brief 创建客户端实例** @param [in] url 服务器地址(服务器地址url需要填写完整,例如(服务器url仅为格式示…...
【状态估计】线性高斯系统的状态估计——离散时间的递归滤波
前两篇文章介绍了离散时间的批量估计、离散时间的递归平滑,本文着重介绍离散时间的递归滤波。 前两篇位置:【状态估计】线性高斯系统的状态估计——离散时间的批量估计、【状态估计】线性高斯系统的状态估计——离散时间的递归平滑。 离散时间的递归滤波…...
架构设计上中的master三种架构,单节点,主从节点,多节点分析
文章目录 背景单节点优点缺点 主从节点优点缺点 多节点优点缺点 多节点,多backup设计优点缺点 总结 背景 在很多分布式系统里会有master,work这种结构。 master 节点负责管理资源,分发任务。下面着重讨论下master 数量不同带来的影响 单节点 优点 1.设…...
如何在 SQL 中删除一条记录?
如何在 SQL 中删除一条记录? 在 SQL 中,您可以使用DELETE查询和WHERE子句删除表中的一条记录。在本文中,我将向您介绍如何使用DELETE查询和WHERE子句删除记录。我还将向您展示如何一次从表中删除多条记录 如何在 SQL 中使用 DELETE 这是使…...
JavaSE (Java基础):面向对象(上)
8 面向对象 面向对象编程的本质就是:以类的方法组织代码,以对象的组织(封装)数据。 8.1 方法的回顾 package com.oop.demo01;// Demo01 类 public class Demo01 {// main方法public static void main(String[] args) {int c 10…...
flink使用StatementSet降低资源浪费
背景 项目中有很多ods层(mysql 通过cannal)kafka,需要对这些ods kakfa做一些etl操作后写入下一层的kafka(dwd层)。 一开始采用的是executeSql方式来执行每个ods→dwd层操作,即类似: def main(…...
FineDataLink4.1.9支持Kettle调用
FDL更新至4.1.9后,新增kettle调用功能,支持不增加额外负担的情况下,将现有的Kettle任务平滑迁移到FineDataLink。 一、更新版本前存在的问题与痛点 在此次功能更新前,用户可能会遇到以下问题: 1.对于仅使用kettle的…...
SwanLinkOS首批实现与HarmonyOS NEXT互联互通,软通动力子公司鸿湖万联助力鸿蒙生态统一互联
在刚刚落下帷幕的华为开发者大会2024上,伴随全场景智能操作系统HarmonyOS Next的盛大发布,作为基于OpenHarmony的同根同源系统生态,软通动力子公司鸿湖万联全域智能操作系统SwanLinkOS首批实现与HarmonyOS NEXT互联互通,率先攻克基…...
Win11禁止右键菜单折叠的方法
背景 在使用windows11的时候,会发现默认情况下,右键菜单折叠了。以至于在使用一些软件的右键菜单时总是要点击“显示更多选项”菜单展开所有菜单,然后再点击。而且每次在显示菜单时先是全部展示,再隐藏一下,看着着实难…...
Maven列出所有的依赖树
在 IntelliJ IDEA 中,你可以使用 Maven 插件来列出项目的依赖树。Maven 插件提供了一个名为dependency:tree的目标,可以帮助你获取项目的依赖树详细信息。 要列出项目的依赖树,可以执行以下步骤: 打开 IntelliJ IDEA,…...
测试开发面试题和答案
Python 请解释Python中的列表推导式(List Comprehension)是什么,并给出一个示例。 答案: 列表推导式是Python中一种简洁的构建列表的方法。它允许从一个已存在的列表创建新列表,同时应用一个表达式来修改或选择元素。…...
llm学习-3(向量数据库的使用)
1:数据读取和加载 接着上面的常规操作 加载环境变量---》获取所有路径---》加载文档---》切分文档 代码如下: import os from dotenv import load_dotenv, find_dotenvload_dotenv(find_dotenv()) # 获取folder_path下所有文件路径,储存在…...
【01-02】Mybatis的配置文件与基于XML的使用
1、引入日志 在这里我们引入SLF4J的日志门面,使用logback的具体日志实现;引入相关依赖: <!--日志的依赖--><dependency><groupId>org.slf4j</groupId><artifactId>slf4j-api</artifactId><version&g…...
Linux-进程间通信(IPC)
进程间通信(IPC)介绍 进程间通信(IPC,InterProcess Communication)是指在不同的进程之间传播或交换信息。IPC 的方式包括管道(无名管道和命名管道)、消息队列、信号量、共享内存、Socket、Stre…...
在鸿蒙HarmonyOS 5中实现抖音风格的点赞功能
下面我将详细介绍如何使用HarmonyOS SDK在HarmonyOS 5中实现类似抖音的点赞功能,包括动画效果、数据同步和交互优化。 1. 基础点赞功能实现 1.1 创建数据模型 // VideoModel.ets export class VideoModel {id: string "";title: string ""…...
【Linux】C语言执行shell指令
在C语言中执行Shell指令 在C语言中,有几种方法可以执行Shell指令: 1. 使用system()函数 这是最简单的方法,包含在stdlib.h头文件中: #include <stdlib.h>int main() {system("ls -l"); // 执行ls -l命令retu…...
iPhone密码忘记了办?iPhoneUnlocker,iPhone解锁工具Aiseesoft iPhone Unlocker 高级注册版分享
平时用 iPhone 的时候,难免会碰到解锁的麻烦事。比如密码忘了、人脸识别 / 指纹识别突然不灵,或者买了二手 iPhone 却被原来的 iCloud 账号锁住,这时候就需要靠谱的解锁工具来帮忙了。Aiseesoft iPhone Unlocker 就是专门解决这些问题的软件&…...
渲染学进阶内容——模型
最近在写模组的时候发现渲染器里面离不开模型的定义,在渲染的第二篇文章中简单的讲解了一下关于模型部分的内容,其实不管是方块还是方块实体,都离不开模型的内容 🧱 一、CubeListBuilder 功能解析 CubeListBuilder 是 Minecraft Java 版模型系统的核心构建器,用于动态创…...
基于matlab策略迭代和值迭代法的动态规划
经典的基于策略迭代和值迭代法的动态规划matlab代码,实现机器人的最优运输 Dynamic-Programming-master/Environment.pdf , 104724 Dynamic-Programming-master/README.md , 506 Dynamic-Programming-master/generalizedPolicyIteration.m , 1970 Dynamic-Programm…...
微软PowerBI考试 PL300-在 Power BI 中清理、转换和加载数据
微软PowerBI考试 PL300-在 Power BI 中清理、转换和加载数据 Power Query 具有大量专门帮助您清理和准备数据以供分析的功能。 您将了解如何简化复杂模型、更改数据类型、重命名对象和透视数据。 您还将了解如何分析列,以便知晓哪些列包含有价值的数据,…...
HarmonyOS运动开发:如何用mpchart绘制运动配速图表
##鸿蒙核心技术##运动开发##Sensor Service Kit(传感器服务)# 前言 在运动类应用中,运动数据的可视化是提升用户体验的重要环节。通过直观的图表展示运动过程中的关键数据,如配速、距离、卡路里消耗等,用户可以更清晰…...
SQL慢可能是触发了ring buffer
简介 最近在进行 postgresql 性能排查的时候,发现 PG 在某一个时间并行执行的 SQL 变得特别慢。最后通过监控监观察到并行发起得时间 buffers_alloc 就急速上升,且低水位伴随在整个慢 SQL,一直是 buferIO 的等待事件,此时也没有其他会话的争抢。SQL 虽然不是高效 SQL ,但…...
C#中的CLR属性、依赖属性与附加属性
CLR属性的主要特征 封装性: 隐藏字段的实现细节 提供对字段的受控访问 访问控制: 可单独设置get/set访问器的可见性 可创建只读或只写属性 计算属性: 可以在getter中执行计算逻辑 不需要直接对应一个字段 验证逻辑: 可以…...
c++第七天 继承与派生2
这一篇文章主要内容是 派生类构造函数与析构函数 在派生类中重写基类成员 以及多继承 第一部分:派生类构造函数与析构函数 当创建一个派生类对象时,基类成员是如何初始化的? 1.当派生类对象创建的时候,基类成员的初始化顺序 …...
