一个Noting的例子
虽然平时开发中也用到过Nothing,但是想弄懂Nothing的想法主要来源于下面这段在Sunflower项目中发现的代码,看到这段代码最开始的想法是:Loading是无状态的,这里把Loading设计成object值得学习。后来再看到用到这个类的地方就对Nothing产生了兴趣。
sealed class Result<out T> {
data class Success<out T>(val data: T): Result<T>()
object Loading: Result<Nothing>()
data class Error(val exception: Exception): Result<Nothing>()
}
下面随便写了段用到Result类的代码,可以发现Success的泛型类型是String而Error的泛型类型则是类声明时的Nothing,但是编译器推断出的返回类型却是Result<String>(代码里我直接写出来了),而不是Result<Any>。
fun request(): Result<String> = if ((0..10).random() <= 5) {
Result.Success("haha")
} else {
Result.Error(IOException())
}
有了这个特性之后Result类使用起来就会很方便,如下
when (val result = request()) {
is Result.Success -> println(result.data.length) // 这里获取到的data的类型是String,不需要强转
is Result.Loading -> println("loading...")
is Result.Error -> println(result.exception.message)
}
Nothing到底是什么
关于Nothing的说明可以看下源码以及注释,Nothing类除了一个私有的构造方法外其他什么都没有并且没有被open修饰,所以也不能被继承,确实是Nothing,注释中也说明了Nothing并不存在实例对象,可以用Nothing表示一个不存在的值,比如一个函数返回类型是Nothing那就意味着这个函数永远不会返回,总是会抛出异常。
/**
* Nothing has no instances. You can use Nothing to represent "a value that never exists": for example,
* if a function has the return type of Nothing, it means that it never returns (always throws an exception).
*/
public class Nothing private constructor()
所以当编写一个肯定会抛出异常的方法可以将它的返回类型设置成Nothing,Kotlin中的TODO()函数就是这么做的
public inline fun TODO(): Nothing = throw NotImplementedError()
但是这样写跟直接写一个肯定会抛出异常但是返回类型是Unit的函数有什么区别呢,而且就注释中的这些说明也并不能解释上一小节中提到的Result类的问题。
又查阅了一番后,在[Kotlin Pearls 7] Unit, Nothing, Any (and null)这篇文章中找到了这样一句话:Nothing可以理解为是任何一个类的子类,包括被final修饰的类。
Nothing is a Class (not an Object) which is a subclass of any other class, including the final ones.
这就能很好的说明上一小节中Result的问题了,由于Nothing是String的子类,所以最终返回的类型是Result<String>。
此外也能想到一个肯定会抛出异常的函数返回值是Nothing和Unit的区别:
val map = HashMap<String, String>()
val value0: String? = map["key"]
val value1: String = map["key"] ?: nothingException("value not found.") // 当返回类型是Nothing时最终获取到的类型是String
val value2: Any = map["key"] ?: unitException("value not found.")
fun nothingException(msg: String): Nothing {
throw Exception(msg)
}
fun unitException(msg: String) {
throw Exception(msg)
}
其他用法
有时候云端接口返回的json结构是这样
{
"code": 200
"message": "success"
"data": {
"id": "123"
"name": "xiaoming"
}
}
开发时我们一般会这样编写代码:
data class HttpResponse<out T>(val code: Int, val message: String, val data: T)
data class UserInfo(val id: String, val name: String)
fun getUserInfo(): HttpResponse<UserInfo> {}
但是data字段可能会是空的,这个时候我们可能会选择将data设置成T?,但是这样在代码中就会出现很多非空检查,这个时候将不会返回data字段的接口的泛型类型传入Noting就可以避免这种情况:
data class HttpResponse<out T>(val code: Int, val message: String, val data: T)
fun getNothing(): HttpResponse<Nothing> {}
fun doSomething() {
getNothing().data // 编译器会有警告,提示这是个Nothing,并且没有toString()、hashCode()等方法
}
当然也可以随便传入一个Unit或者Any但是编译器不会有警告。
这个用法也是之前在某个开源项目中看到的,但是当时并没有感觉有什么特别,只是觉得Kotlin有Nothing这个东西,还挺好用的,没有开头提到的Result类的冲击感。