Kotlin的Nothing
类,无法创建出任何示例:
public class Nothing private constructor()
所以一切Nothing
类型的变量或者函数,都找不到可用的值。
val nothing: Nothing = ???
fun nothing(): Nothing {
...
return ???
}
就这么简单。但——它有啥用?
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的源码里都有不少这样的实践,比方Composr的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 = ???
val apple: Apple = nothing
谁也无法用。
可是我假如不直接用Nothing
,而是把它作为泛型类型的实例化参数:
val emptyList: List<Nothing> = ???
var apple: 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
的初始化?有什么优点?既省事,又省内存,这便是优点。
这种用法不只能够用在List
,Set
和Map
也都没问题:
val emptySet: Set<Nothing> = setOf()
var apples: Set<Apple> = emptySet
var users: Set<User> = emptySet
var phone: Set<Phone> = emptySet
var images: Set<Image> = emptySet
val emptyMap: Map<String, Nothing> = mapOf()
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()
的内部是一个throw
:
@kotlin.internal.InlineOnly
public inline fun TODO(): Nothing = throw NotImplementedError()
TODO()
回来的是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)
}
尽管直接强行解释为「return
想怎样写就怎样写」也是能够的,但Kotlin还是扩大了规矩,规矩return
的回来值是Nothing
,让代码从语法层面就能得到解释。
这便是Nothing
的最后一个效果:语法层面你的完好化。
总结
好,Nothing
的界说、定位和用法便是这些。假如没记全,很正常,再看一遍。
版权声明
本文首发于:这玩意真的有用吗?对,是的!Kotlin 的 Nothing 详解
微信公众号:扔物线