科特林有三种性质非常相似的类型:
Void
Unit
Nothing
他们似乎在犯 JavaScript 的错误:
null
undefined
void(0)
假设他们的 没有陷入了同样的错误,他们都是为了什么,他们有什么不同?
它是一个普通的 Java 类,在 Kotlin 没有特殊的意义。
Unit类型只有一个值。取代了 Javavoid(注意: 不是 Void)。更多信息在 Kotlin 医生中。
void
Nothing没有实例(就像 Void一样)。它代表“一个从不存在的价值”。在 Kotlin,如果你抛出一个错误,它是一个 Nothing(见 Kotlin 医生)。
Void类型来自 Java。你通常不会从 Kotlin 使用它,除非你使用一些 Java 库使用它。
Unit类型是从一个不返回任何感兴趣内容的函数返回的值。这种功能通常会产生某种副作用。单元类型只有一个可能的值,即 Unit对象。在 Kotlin 使用 Unit作为返回类型,而在 Java 中使用 void(小写 v)。
Nothing类型没有值。如果函数的返回类型为 Nothing,则它不能正常返回。它要么必须抛出异常,要么必须输入无限循环。对返回类型为 Nothing的函数的调用之后的代码将被 Kotlin 编译器标记为不可达。
因为 Nothing没有值,所以 Nothing?实际上是在 Kotlin 只捕获 null值的类型。
Nothing?
在 Kotlin,当一个函数没有返回任何有意义的值时,它被声明为返回 Unit,就像 Java 中的 void:
fun greet(): Unit { println("Good day!") }
在函数返回 Unit时跳过编写 Unit是一种约定,因为编译器认为 Unit是默认的返回类型:
fun greet() { println("Good day!") }
这个类只有一个对象(单例模式) ,这个对象就是它自己。它在 kotlin包中使用对象声明进行声明,如下所示:
kotlin
public object Unit { override fun toString() = "kotlin.Unit" }
Kotlin 在函数式编程方面拥有一流的支持。在函数式编程语言中使用 Unit是很常见的。它通过使所有函数都声明为具有返回值,使得函数类型更具可读性,即使在函数不返回值的情况下:
val greet: () -> Unit = { println("Good day!") }
在这里,() -> Unit是一个函数类型,->后面的 Unit表示这个函数类型不返回任何有意义的值。在函数类型中不能跳过提到 Unit。
() -> Unit
->
每个函数都必须返回一个值。Kotlin 决定使用 同学们来表示它,而不是 Java 中的特殊类型 void。使用类的原因是,通过使类型系统成为类型层次结构的一部分,可以使类型系统更加一致。
例如,假设我们有一个名为 Worker<T>的通用 interface,它执行一些工作。这个接口的 doWork()功能做一些工作,必须返回一个值 T:
Worker<T>
interface
doWork()
T
interface Worker<T> { fun doWork(): T }
但是有时候,我们可能希望将这个接口用于某些工作,例如,在下面显示的扩展 Worker接口的 LogWorker类中的日志记录工作:
Worker
LogWorker
class LogWorker : Worker<Unit> { override fun doWork() { // Do the logging } }
这就是 Unit的神奇之处,在这里我们能够使用最初设计用于返回值的预先存在的接口。在这里,我们让 doWork()函数返回 Unit值,以满足我们的目的,在这里我们没有任何东西可以返回。因此,当重写返回泛型参数的函数时,它非常有用。
注意,我们还跳过了 doWork()函数的 Unit返回类型。也不需要编写 return语句。
return
在 Kotlin,类 Nothing代表 一个从不存在的价值。这个类永远不可能有任何值/对象,因为它的 constructor保持为 private。它在 kotlin包中定义如下:
constructor
private
public class Nothing private constructor()
Nothing用于从不返回值的函数的返回类型。例如,具有无限循环的函数或总是引发异常的函数。来自 Kotlin 标准库的 error()函数就是一个例子,它总是抛出一个异常并返回 Nothing。下面是它的代码:
error()
fun error(message: Any): Nothing = throw IllegalStateException(message.toString())
在类型理论中,没有值的类型称为底类型,它是所有其他类型的子类型。因此,在 Kotlin,Nothing是所有类型的 亚型,就像 Any?是所有类型的 超模一样。因此,Nothing类型的值(从不存在)可以赋给所有类型的变量,例如:
Any?
val user: User = request.user ?: error("User not found")
在这里,我们使用 elvis 操作符(?:)调用前面定义的 error()函数(如果 user是 null)。error()函数返回类型为 Nothing的值,但是可以将其赋给类型为 User的变量,因为 Nothing是 User的一个子类型,就像它是任何其他类型的一个子类型一样。编译器允许这样做,因为它知道 error()函数永远不会返回一个值,所以不会造成损害。
?:
user
User
类似地,可以从具有任何其他返回类型的函数返回 Nothing:
fun getUser(request: Request): User { return request.user ?: error("User not found") }
在这里,即使 getUser()函数被声明为返回 User,如果 user是 null,它也可以返回 Nothing。
getUser()
考虑下面的函数示例,该函数删除列表中给出的文件:
fun deleteFiles(files: List<File>? = null) { if (files != null) files.forEach { it.delete() } }
这个函数设计的问题在于它不能表示 List<File>是空的、 null还是有元素。另外,在使用该列表之前,我们需要检查它是否是 null。
List<File>
为了解决这个问题,我们使用空对象设计模式。在空对象模式中,我们不使用 null引用来表示没有对象,而是使用一个对象来实现预期的接口,但是留下方法体为空。
因此,我们定义了接口 List<Nothing>的对象:
List<Nothing>
// This function is already defined in the Kotlin standard library fun emptyList() = object : List<Nothing> { override fun iterator(): Iterator<Nothing> = EmptyIterator ... }
现在我们在 deleteFiles()函数中使用这个 null 对象作为参数的默认值:
deleteFiles()
fun deleteFiles(files: List<File> = emptyList()) { files.forEach { it.delete() } }
这消除了 null或空的不确定性,使意图更加清晰。它还删除了 null 检查,因为 null 对象上的函数是空的,它们将被调用,但它们是 禁止行动(它们中没有操作,因此它们什么也不做)。
在上面的例子中,编译器允许我们在需要 List<File>的地方传递 List<Nothing>。这是因为 Kotlin 的 List接口是协变的,因为它使用的是 out关键字,也就是 List<out T>。据了解,Nothing是所有类型的亚型,Nothing也是 File的亚型。由于协方差,List<Nothing>是 List<File>、 List<File>0、 List<File>1等等的一个亚型... ... List<File>2。这适用于具有协变泛型(out)的任何类型,而不仅仅是 List。
List
out
List<out T>
File
就像我们示例中使用的函数 emptyList()一样,有一些预定义的函数,如 emptyMap()、 emptySet()、 emptySequence(),它们返回 null 对象。所有这些都是使用 Nothing定义的。您可以像这样定义自己的对象。
emptyList()
emptyMap()
emptySet()
emptySequence()
这里的优点是,这些返回单例对象,例如,您可以调用相同的 emptyList()函数来获得一个空实例,无论它是分配给 List<File>、 List<Int>和... List<AllTypes>还是在多个位置。由于每次都返回相同的对象,因此可以节省对象创建和内存分配的成本。
List<Int>
List<AllTypes>
Void类来自 java.lang包,而 Unit和 Nothing来自 kotlin包。ABc0并不打算在 Kotlin 使用。Kotlin 也有自己的课程,以 ABc2的形式。
java.lang
在 Java 中,Void用于扩展泛型接口,比如为 Unit编写的 Worker接口示例,其中必须返回一个值。因此,为了将 Kotlin 代码转换成 Java,我们可以使用 Void,就像我们在 Worker示例中使用 Unit一样,然后用 Java 重写代码,如下所示:
interface Worker<T> { T doWork(); } class LogWorker implements Worker<Void> { @Override public Void doWork() { // Do the logging return null; } }
请注意,在使用 Void时,我们必须使用 Void作为返回类型(不能跳过) ,并且需要编写 return语句,而对于 Unit,我们可以两者都跳过。这是在 Kotlin 代码中避免使用 Void的另一个原因。
因此,在我看来,Unit和 Nothing不是 Kotlin 设计师的错误,也不像 Javascript 中的 null、 undefined和 void(0)那样有问题。Unit和 Nothing使函数式编程变得轻而易举,同时提供了前面提到的其他有用特性。它们在其他函数式编程语言中也很常见。
就是这样,希望能有帮助。