Unit
Unit 就像void
在 Kotlin 中,当一个函数没有返回任何有意义的值时,它被声明为返回 Unit,就像 Java 中的 void:
fun greet(): Unit { println("Good day!") }
当函数返回 Unit 时跳过写入 Unit 是惯例,因为 Unit 被编译器视为默认返回类型:
fun greet() { println("Good day!") }
Unit 是单例
Unit 是一个只有一个对象(单例模式)的类,而该对象就是Unit 本身。它在kotlin 包中使用对象声明进行声明,如下所示:
public object Unit {
override fun toString() = "kotlin.Unit"
}
Unit 在函数式编程中
Kotlin 对函数式编程具有一流的支持。在函数式编程语言中使用Unit 是很常见的。通过使所有函数都可以声明为具有返回值,即使函数没有返回值,它也使函数类型更具可读性:
val greet: () -> Unit = { println("Good day!") }
这里() -> Unit是一个函数类型,->后面的Unit表示这个函数类型没有返回任何有意义的值。在函数类型中不能跳过提及Unit。
Unit 用于扩展泛型
每个函数都必须返回一个值。 Kotlin 决定用 class 来表示它,而不是像 Java 中那样用特殊类型 void 来表示。使用类的原因是类型系统可以通过使其成为类型层次结构的一部分而更加一致。
例如,假设我们有一个名为Worker<T> 的通用interface,它执行一些工作。这个接口的doWork()函数做了一些工作,必须返回一个值T:
interface Worker<T> {
fun doWork(): T
}
但有时,我们可能希望将此接口用于一些工作我们不需要返回任何值,例如日志记录工作,在下面显示的LogWorker 类中扩展了Worker 接口:
class LogWorker : Worker<Unit> {
override fun doWork() {
// Do the logging
}
}
这就是Unit 的魔力,我们可以使用原先设计为返回值的预先存在的接口。在这里,我们使doWork() 函数返回Unit 值,以达到我们没有任何返回值的目的。 所以,当你重写一个返回泛型参数的函数时,它很有用。
请注意,我们还没有提及 Unit 函数的 doWork() 返回类型。也不需要写return 语句。
Nothing
Nothing 的价值永远不存在
在 Kotlin 中,Nothing 类表示一个永远不存在的值。这个类永远不会有任何值/对象,因为它的constructor 保留为private。它在kotlin 包中定义如下:
public class Nothing private constructor()
Nothing 用于从不返回值的函数的返回类型。例如,具有无限循环的函数或总是抛出异常的函数。 Kotlin 标准库中的 error() 函数就是一个总是抛出异常并返回 Nothing 的示例。这是它的代码:
fun error(message: Any): Nothing = throw IllegalStateException(message.toString())
Nothing 是底部类型
在函数式编程中,没有值的类型称为底部类型,它是所有其他类型的子类型。所以,Nothing 是 Kotlin 中所有类型的 subtype,就像 Any? 是所有类型的 supertype。因此,Nothing 类型的值(永远不存在)可以分配给所有类型的变量,例如:
val user: User = request.user ?: error("User not found")
在这里,如果user 是null,我们使用elvis 运算符(?:) 调用我们之前定义的error() 函数。 error() 函数返回Nothing 类型的值,但它可以分配给User 类型的变量,因为Nothing 是User 的子类型,就像它是任何其他类型的子类型一样。编译器允许这样做是因为它知道error() 函数永远不会返回值,所以没有害处。
同样,您可以从具有任何其他返回类型的函数返回 Nothing:
fun getUser(request: Request): User {
return request.user ?: error("User not found")
}
在这里,即使 getUser() 函数被声明为返回 User,它也可能返回 Nothing,如果 user 是 null。
Nothing 在空对象模式中
考虑以下删除列表中给定文件的函数示例:
fun deleteFiles(files: List<File>? = null) {
if (files != null) files.forEach { it.delete() }
}
这个函数设计的问题是它没有传达List<File>是空的还是null或者有元素。另外,我们需要在使用前检查列表是否为null。
为了解决这个问题,我们使用空对象设计模式。在空对象模式中,我们不使用null 引用来表示对象的缺失,而是使用实现预期接口的对象,但将方法体留空。
那么,我们定义接口List<Nothing>的对象:
// This function is already defined in the Kotlin standard library
fun emptyList() = object : List<Nothing> {
override fun iterator(): Iterator<Nothing> = EmptyIterator
...
}
现在我们在 deleteFiles() 函数中使用这个空对象作为我们参数的默认值:
fun deleteFiles(files: List<File> = emptyList()) {
files.forEach { it.delete() }
}
这消除了null或空的不确定性,使意图更清晰。它还删除了 null 检查,因为 null 对象上的函数是空的,它们将被调用,但它们是 no-ops(其中没有操作,所以它们什么都不做)。
Nothing 用于协变泛型
在上面的示例中,编译器允许我们传递List<Nothing>,其中应该是List<File>。这是因为 Kotlin 中的 List 接口是协变的,因为它是使用 out 关键字定义的,即 List<out T>。正如我们所知,Nothing 是所有类型的子类型,Nothing 也是File 的子类型。并且由于协方差,List<Nothing> 是List<File>、List<Int>、List<User> 等的子类型...List<AllTypes>。这适用于具有协变泛型 (out) 的任何类型,而不仅仅是 List。
Nothing 以获得更好的性能
就像我们示例中使用的函数 emptyList() 一样,有一些预定义的函数,如 emptyMap()、emptySet()、emptySequence(),它们会返回空对象。所有这些都是使用Nothing 定义的。您可以像这样定义自己的对象。
这里的好处是这些返回单例对象,例如,您可以调用相同的emptyList()函数来获取一个空实例,无论是分配给List<File>,List<Int>和...@987654428 @ 并在多个地方。由于每次都返回相同的对象,因此节省了对象创建和内存分配的成本。
Void
Void 用于在 Java 中扩展泛型
Void 类来自java.lang 包,而Unit 和Nothing 来自kotlin 包。 Void 不打算在 Kotlin 中使用。 Kotlin 有自己的类,形式为Unit。
Void 在 Java 中用于扩展通用接口,例如为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 使函数式编程变得轻而易举,同时提供了提到的其他有用功能。它们在其他函数式编程语言中也很常见。
就是这样!希望对您有所帮助。