这玩意真的有用吗?对,是的!Kotlin 的 Nothing 详解

news2024/7/6 17:51:41

视频先行

下面是视频内容的脚本文案原稿分享。

ad18b34ed3673da580e6a25b998fec2e.jpeg

文案原稿

Kotlin 的 Nothing 类,无法创建出任何实例:

public class Nothing private constructor()

所以所有 Nothing 类型的变量或者函数,都找不到可用的值:

val nothing: Nothing = ???
fun nothing(): Nothing {
  ...
  return ???
}

就这么简单。但——它有啥用啊?

Nothing 的本质

大家好,我是扔物线朱凯。上期讲了 Kotlin 的 Unit,这期讲 Nothing。 Nothing 的源码很简单:

public class Nothing private constructor()

可以看到它本身虽然是 public 的,但它的构造函数是 private 的,这就导致我们没法创建它的实例;而且它不像 Unit 那样是个 object

public object Unit {
  override fun toString() = "kotlin.Unit"
}

而是个普通的 class;并且在源码里 Kotlin 也没有帮我们创建它的实例。

这些条件加起来,结果就是:Nothing 这个类既没有、也不会有任何的实例对象。

基于这样的前提,当我们写出这个函数声明的时候:

fun nothing(): Nothing {

}

我们不可能找到一个合适的值来返回。你必须返回一个值,但却永远找不到合适的返回值。悖论了。

作用一:作为函数「永不返回」的提示

怎么办?

不怎么办。这个悖论,就是 Nothing 存在的意义:它找不到任何可用的值,所以,以它为返回值类型的一定是个不会返回的函数,比如——它可以总是抛异常。 什么意思?就是说,我这么写是可以的:

fun nothing() : Nothing {
  throw RuntimeException("Nothing!")
}

这个写法并没有返回任何结果,而是抛异常了,所以是合法的。

可能有的人会觉得有问题:抛异常就可以为所欲为吗?抛异常就可以忽略返回值了吗?——啊对,抛异常就是可以忽略返回值,而且这不是 Nothing 的特性,而是本来就是这样,而且你本来就知道,只是到这里的时候,你可能会忘了。 例如这个写法:

fun getName() : String {
  if (nameValue != null) {
    return nameValue
  } else {
    throw NullPointerException("nameValue 不能为空!")
  }
}

——其实这个函数可以有更加简洁的写法:

fun getName() = nameValue ?: throw NullPointerException("nameValue 不能为空!")

不过我们为了方便讲解,就不简化了:

fun getName() : String {
  if (nameValue != null) {
    return nameValue
  } else {
    throw NullPointerException("nameValue 不能为空!")
  }
}

在这个函数里,一个 if 判断,true 就返回,false 就抛异常,这个写法很常见吧?它在 else 的这个分支,是不是就只抛异常而不返回值了?实际上 Java 和 Kotlin 的任何方法或者说函数,在抛异常的时候都是不返回值的——你都抛异常的还返回啥呀返回?是吧?

所以我如果改成这样:

fun getName() : String {
  throw NullPointerException("不能为空!")
}

其实也是可以的。只是看起来比较奇怪罢了,会让人觉得「怎么会这么写呢」?但它的写法本身是完全合法的。而且如果我把函数的名字改一下,再加个注释:

/**
 当遇到姓名为空的时候,请调用这个函数来抛异常
*/
fun throwOnNameNull() : String {
  throw NullPointerException("姓名不能为空!")
}

这就很合理了吧?不干别的,就只是抛异常。这是一种很常用的工具函数的写法,包括 Kotlin 和 Compose 的官方源码里也有这种东西。

那么我们继续来看它的返回值类型:我都不返回了,就没必要还写 String 了吧?那写什么?可以把它改成 Unit

/**
 当任何变量为空的时候,请统一调用这个函数来抛异常
*/
fun throwOnNameNull() : Unit {
  throw NullPointerException("姓名不能为空!")
}

有问题吗?没问题。

不过,Kotlin 又进了一步,提供了一个额外的选项:你还可以把它改成 Nothing

/**
 当任何变量为空的时候,请统一调用这个函数来抛异常
*/
fun throwOnNameNull() : Nothing {
  throw NullPointerException("姓名不能为空!")
}

虽然我找不到 Nothing 的实例,但是这个函数本来就是永远抛异常的,找不到实例也没关系。哎,这不就能用了吗?对吧?

不过,能用归能用,这么写有啥意义啊?是吧?价值在哪?——价值就在于,Nothing 这个返回值类型能够给使用它的开发者一个明确的提示:这是个永远不会返回的函数。这种提示本身,就会给开发提供一些方便,它能很好地避免函数的调用者对函数的误解而导致的一些问题。我们从 Java 过来的人可能第一时间不太能感受到这种东西的用处,其实你要真说它作用有多大吧,我觉得不算大,主要是很方便。它是属于「你没有的话也不觉得有什么不好的,但是有了之后就再也不想没有它」的那种小方便。就跟 120Hz 的屏幕刷新率有点像,多少带点毒。

Kotlin 的源码、Compose 的源码里都有不少这样的实践,比如 Compose 的 noLocalProviderFor() 函数:

private fun noLocalProvidedFor(name: String): Nothing {
  error("CompositionLocal $name not present")
}

好,这就是 Nothing 的作用之一:作为函数的返回值类型,来明确表达「这是个永不返回的函数」。

其实 Nothing 的「永不返回」除了抛异常之外,还有一种场景,就是无限循环:

fun foreverRepeat(): Nothing {
  while (true) {
    ...
  }
}

不过一般很少有人这么去用,大部分都是用在我刚才说的抛异常的场景,这是非常常见的一种用法,你写业务可能用不到,但是基础架构团队给全公司写框架或者对外写 SDK 的话,用到它的概率就非常大了。

作用二:作为泛型对象的临时空白填充

另外 Nothing 除了「没有可用的实例」之外,还有个特性:它是所有类型共同的子类型。这其实是违反了 Kotlin 的「类不允许多重继承」的规定的,但是 Kotlin 强行扩充了规则:Nothing 除外,它不受这个规则的约束。虽然这违反了「类不允许多重继承」,但因为 Nothing 不存在实例对象,所以它的多重继承是不会带来实际的风险的。——我以前还跟人说「Nothing 是所有类型的子类型」这种说法是错误的,惭愧惭愧,是我说错了。

不过,这个特性又有什么作用呢?它就能让你对于任何变量的赋值,都可以在等号右边写一个 Nothing

val nothing: Nothing = TODO()
var apple: Apple = nothing

这儿其实有个问题:我刚说了 Nothing 不会有任何的实例,对吧?那么这个右边就算能填 Nothing 类型的对象,可是这个对象我用谁啊?

val nothing: Nothing = ???
var apple: Apple = nothing

谁也没法用。

但是我如果不直接用 Nothing,而是把它作为泛型类型的实例化参数:

val emptyList: List<Nothing> = ???
var apples: List<Apple> = emptyList

这就可以写了。一个元素类型为Nothing 的 List,将会导致我无法找到任何的元素实例来填充进去,但是这个 List 本身是可以被创建的:

val emptyList: List<Nothing> = listOf()
var apples: List<Apple> = emptyList

只不过这种写法看起来好像有点废,因为它永远都只能是一个空的 List。但是,如果结合上我们刚说的「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 的初始化?有什么好处?既省事,又省内存,这就是好处。

这种用法不只可以用在 ListSet 和 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 可以创建出一个通用的「空白」对象,它什么实质内容也没有,什么实质工作也做不了,但可以用来作为泛型变量的一个通用的空白占位值。这就是 Nothing 的第二个主要用处:作为泛型变量的通用的、空白的临时填充。多说一句:这种空白的填充一定是临时的才有意义,你如果去观察一下就会发现,这种用法通常都是赋值给 var 属性,而不会赋值给 val

val emptyProducer: Producer<Nothing> = Producer()
// 没人这么写:
val appleProducer: Producer<Apple> = emptyProducer
val userProducer: Producer<User> = emptyProducer
val phoneProducer: Producer<Phone> = emptyProducer
val imageProducer: Producer<Image> = emptyProducer

因为赋值给 val 那就是永久的「空白」了,永久的空白那不叫空白,叫废柴,这个变量就没意义了。

作用三:语法的完整化

另外,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,我们就可以把它写在等号的右边,在语法层面假装成一个值来使用,但其实目的是在例外情况时抛异常。

Kotlin 里面有个 TODO() 函数对吧:

val someValue: String = TODO()

这种写法不会报错,并不是 IDE 或者编译器做了特殊处理,而是因为 TODO() 的内部是一个 throwfbbc604a95b99c951be2d9d71a49798c.jpegTODO() 返回的是 Nothing,是 String 的子类,怎么不能写了?完全合法!虽然 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)
}

这段代码也是可以简化的:

fun sayMyName(first: String, second: String) {
  if (first == "Walter" && second == "White") println("Heisenberg")
}

不过同样,咱不是为了讲东西么,就不简化了:

fun sayMyName(first: String, second: String) {
  val name = if (first == "Walter" && second == "White") {
    "Heisenberg"
  } else {
    return // 语法层面的返回值类型为 Nothing,赋值给 name
  }
  println(name)
}

虽然直接强行解释为「return 想怎么写就怎么写」也是可以的,但 Kotlin 还是扩充了规则,规定 return 的返回值是 Nothing,让代码从语法层面就能得到解释。

这就是 Nothing 的最后一个作用:语法层面的完整化。

总结

好,Nothing 的定义、定位和用法就是这些。如果没记全,很正常,再看一遍。你看视频花的时间一定没有我研究它花的时间多,所以多看两遍应该不算浪费时间。 下期我会讲一个很多人不关注但很有用的话题:Kotlin 的数值系统,比如 Float 和 Double 怎么选、为什么 0.7 / 5.0 ≠ 0.14 这类的问题。关注我,了解更多 Android 开发相关的知识和技能。我是扔物线,我不和你比高低,我只助你成长。我们下期见!

90dbbced1e3c9ba02a0333a7d061757f.jpeg

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

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

相关文章

电脑视频录屏软件哪个好用 电脑视频录屏怎么录屏

录屏是我们的工作和生活中非常大的一个需求&#xff0c;尤其对于专业的视频制作者来说&#xff0c;经常需要录制屏幕和编辑视频&#xff0c;因此找到便捷好用的录屏软件非常重要。今天就来分享一下电脑视频录屏软件哪个好用&#xff0c;电脑视频录屏怎么录屏。 一、电脑视频录…

2023 年第八届数维杯大学生数学建模挑战赛 B 题 节能列车运行控制优化策略

在城市交通电气化进程快速推进的同时&#xff0c;与之相应的能耗增长和负面效应也 在迅速增加。城市轨道交通中的快速增长的能耗给城轨交通的可持续性发展带来 负担。2018 年&#xff0c;北京、上海、广州地铁负荷占全市总负荷的 1.5%-2.5%,成为了 城市电网的最大单体负荷[1]。…

软考A计划-真题-分类精讲汇总-第一章(计算机组成原理与体系结构)

点击跳转专栏>Unity3D特效百例点击跳转专栏>案例项目实战源码点击跳转专栏>游戏脚本-辅助自动化点击跳转专栏>Android控件全解手册点击跳转专栏>Scratch编程案例 &#x1f449;关于作者 专注于Android/Unity和各种游戏开发技巧&#xff0c;以及各种资源分享&am…

Java每日一练(20230512) 最大间距、串联子串、最长回文子串

目录 1. 最大间距 &#x1f31f;&#x1f31f;&#x1f31f; 2. 串联所有单词的子串 &#x1f31f;&#x1f31f;&#x1f31f; 3. 最长回文子串 &#x1f31f;&#x1f31f; &#x1f31f; 每日一练刷题专栏 &#x1f31f; Golang每日一练 专栏 Python每日一练 专栏 …

c++ 11标准模板(STL) std::vector<bool> (二)

定义于头文件 <vector> template<class Allocator> class vector<bool, Allocator>; std::vector<bool> 是 std::vector 对类型 bool 为空间提效的特化。 std::vector<bool> 中对空间提效的行为&#xff08;以及它是否有优化&#xff09;是实现…

使用ChatGPT完成部门和个人季度工作总结

“疫后元年“2023年的第一季度即将过去&#xff0c;相信各位社会人们都过得比往年更加紧张充实吧&#xff1f;转眼又到了完成季度工作总结的时间&#xff0c;完成本职工作之余还要抽出专门的时间完成优秀的工作总结报告&#xff0c;又更让大家本不富裕的休息时间雪上加霜。那么…

SCS【24】单细胞数据量化代谢的计算方法 (scMetabolism)

桓峰基因公众号推出单细胞生信分析教程并配有视频在线教程&#xff0c;目前整理出来的相关教程目录如下&#xff1a; Topic 6. 克隆进化之 Canopy Topic 7. 克隆进化之 Cardelino Topic 8. 克隆进化之 RobustClone SCS【1】今天开启单细胞之旅&#xff0c;述说单细胞测序的前世…

Oracle Cloud和足球

Oracle除了我们熟知的数据库产品&#xff0c;它还有很多技术和产品栈&#xff0c;而且在实践层面&#xff0c;已经有了很多可借鉴的案例&#xff0c;如果了解英超的朋友&#xff0c;可以发现最近几个赛季&#xff0c;英超的转播画面图标中&#xff0c;会出现Oracle Cloud&#…

操作系统的最强入门科普(Unix/Linux篇)

大家好&#xff0c;我是小枣君。 今天这篇文章&#xff0c;我们来聊聊操作系统&#xff08;Operating System&#xff09;。 说到操作系统&#xff0c;大家都不会陌生。我们天天都在接触操作系统——用台式机或笔记本电脑&#xff0c;使用的是windows和macOS系统&#xff1b;用…

为什么拿低绩效的人从来不反思自己的原因?连公司的前90%都挤不进去,还好意思找别人的理由?...

绩效是关乎打工人切身利益的大事&#xff0c;拿了高绩效欢天喜地&#xff0c;拿了低绩效垂头丧气&#xff0c;这是人之常情&#xff0c;但最近一位字节跳动的程序员却怒斥那些拿低绩效的人&#xff1a; 为什么拿m-&#xff08;低绩效&#xff09;的人从来不反思自己垫底的原因&…

C++三部曲|C++核心思想

| 导语 C 的起源可以追溯到 40 年前&#xff0c;但它仍然是当今使用最广泛的编程语言之一&#xff0c;C发明人Bjarne Stroustrup 一开始没想到 C 会获得如此大的成功&#xff0c;他说&#xff1a;“C 的成功显然令人惊讶。我认为它的成功取决于其最初的设计目标&#xff0c;就是…

前端014_标签模块_修改功能

标签模块_修改功能 1、需求分析2、Mock 添加查询数据3、Mock 添加提交修改数据4、Api 调用接口回显数据5、提交修改后的数据6、验证1、需求分析 当点击 编辑 按钮后,弹出编辑窗口,并查询出标签信息渲染。修改后点击确定 提交修改数据。 2、Mock 添加查询数据 请求URL: /a…

在OpenCV中使用Canny边缘检测

边缘检测是非常常见和广泛使用的图像处理&#xff0c;对于许多不同的计算机视觉应用非常必要&#xff0c;如数据提取&#xff0c;图像分割&#xff0c;在更细粒度的特征提取和模式识别中。它降低了图像中的噪声和细节数量&#xff0c;但保留了图像的结构。 Python中的Canny边缘…

祁宁:社区问答是激荡企业高级智慧的头脑风暴 | 开发者说

在祁宁家里&#xff0c;有一套完整的赛车模拟器&#xff0c;他甚至还请人到国外代购了最新的 VR 设备。作为沉浸式赛车游戏发烧友&#xff0c;除了享受速度与激情带来的愉悦感&#xff0c;祁宁在玩的过程中更多的是思考如何将技术能力进行产品化的问题。 Answer.dev 就是将技术…

Studio Bot - 让 AI 帮我写 Android 代码

Google I/O 2023 不出所料&#xff0c;今年的 I/O 大会只突出了一个关键词 “AI”。Google 旗下的各类产品都成了其展示 AI 实力的舞台。连面向开发者的产品 Android Studio 也新增了 Studio bot 功能&#xff0c;希望通过 AI 帮助开发者更轻松地写代码&#xff1a; Studio Bot…

STM32F4_内部温度传感器

目录 1. 什么是温度传感器 2. 温度传感器简介 3. 库函数配置内部温度传感器 4. 实验程序 4.1 main.c 4.2 ADC.c 4.3 ADC.h 1. 什么是温度传感器 温度传感器是一种测量物体冷热程度的设备&#xff0c;以可读的方式通过电信号提供温度测量。比较常见的如热电偶温度传感器。…

越来越多的单身女性开始买房了

近日&#xff0c;越来越多的单身女性开始购买房产&#xff0c;这一现象引起了社会的广泛关注。 根据中国青年报社会调查中心联合问卷网对2006名受访者进行的一项调查显示&#xff0c;84.2%的受访者认为近几年身边单身女性购房的现象增多了&#xff0c;而93.9%的受访者支持单身女…

无监督域适应 (UDA)(3)

本帖介绍基于 bi-classifier adversarial learning 的改进。 一、动机 模型训练在两个步骤之间交替进行:(I)约束两个分类器的学习&#xff0c;以最大化未标记目标域数据的预测差异;(II)约束特征提取器的学习&#xff0c;以最小化这种差异。 尽管这种方法是一种优雅的表述&am…

Call for Papers丨第一届LLM@IJCAI‘23 Symposium,大会主席陈雷、杨强、唐杰

大规模语言模型&#xff08;LLMs&#xff09;&#xff0c;如ChatGPT和GPT-4&#xff0c;以其在自然语言理解和生成方面的卓越能力&#xff0c;彻底改变了人工智能领域。 LLMs广泛用于各种应用&#xff0c;如语音助手、推荐系统、内容生成模型&#xff08;如ChatGPT&#xff09…

开源进展 |WeBASE更新啦,快来了解新特性!

WeBASE&#xff08;WeBank Blockchain Application Software Extension&#xff09; 是在区块链应用和FISCO BCOS底层之间搭建的一套通用组件&#xff0c;围绕交易、合约、密钥管理、数据、可视化管理来设计各个模块&#xff0c;屏蔽了区块链底层的复杂度&#xff0c;从而极大降…