【问题标题】:Kotlin - Void vs. Unit vs. NothingKotlin - Void vs. Unit vs. Nothing
【发布时间】:2019-09-21 00:16:13
【问题描述】:

Kotlin 具有三种性质非常相似的类型:

  • Void
  • Unit
  • Nothing

似乎他们犯了 JavaScript 错误:

  • null
  • undefined
  • void(0)

假设他们没有陷入同样的​​错误,他们都是为了什么,他们有什么不同?

【问题讨论】:

  • @mobibob 我相信 Kotlin 的基本原理是所有函数 return 一个对象,即使该对象是 Unit。相比之下,Java 中的 IIRC,void 不是任何类型的对象,而不是没有对象。此外,Unit 自 Kotlin 1.0 以来一直存在,而 UInt 是在 Kotlin 1.3 中引入的。您总是会在所有语言中找到非常相似的词(例如,英语中的“there”、“their”和“they're”,但人们仍然经常混淆这些词)。

标签: kotlin generics types functional-programming


【解决方案1】:

Void 类型来自 Java。您通常不会在 Kotlin 中使用它,除非您正在使用一些使用它的 Java 库。

Unit 类型是您从不返回任何感兴趣的函数的函数返回的内容。这样的功能通常会执行某种副作用。单位类型只有一个可能的值,即the Unit object。当您在 Java 中使用 void(小写 v)时,您在 Kotlin 中使用 Unit 作为返回类型。

Nothing 类型没有值。如果函数返回类型为Nothing,则无法正常返回。它要么必须抛出异常,要么进入无限循环。调用返回类型为 Nothing 的函数之后的代码将被 Kotlin 编译器标记为不可访问。

因为Nothing 没有值,所以Nothing? 实际上是Kotlin 中只捕获null 值的类型。

【讨论】:

    【解决方案2】:

    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&lt;File&gt;是空的还是null或者有元素。另外,我们需要在使用前检查列表是否为null。

    为了解决这个问题,我们使用空对象设计模式。在空对象模式中,我们不使用null 引用来表示对象的缺失,而是使用实现预期接口的对象,但将方法体留空。

    那么,我们定义接口List&lt;Nothing&gt;的对象:

    // 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&lt;Nothing&gt;,其中应该是List&lt;File&gt;。这是因为 Kotlin 中的 List 接口是协变的,因为它是使用 out 关键字定义的,即 List&lt;out T&gt;。正如我们所知,Nothing 是所有类型的子类型,Nothing 也是File 的子类型。并且由于协方差,List&lt;Nothing&gt; 是List&lt;File&gt;、List&lt;Int&gt;、List&lt;User&gt; 等的子类型...List&lt;AllTypes&gt;。这适用于具有协变泛型 (out) 的任何类型,而不仅仅是 List。


    Nothing 以获得更好的性能

    就像我们示例中使用的函数 emptyList() 一样,有一些预定义的函数,如 emptyMap()、emptySet()、emptySequence(),它们会返回空对象。所有这些都是使用Nothing 定义的。您可以像这样定义自己的对象。

    这里的好处是这些返回单例对象,例如,您可以调用相同的emptyList()函数来获取一个空实例,无论是分配给List&lt;File&gt;,List&lt;Int&gt;和...@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 使函数式编程变得轻而易举,同时提供了提到的其他有用功能。它们在其他函数式编程语言中也很常见。

    就是这样!希望对您有所帮助。

    【讨论】:

      【解决方案3】:

      Void 是不可实例化的类型。它是一个普通的 Java 类,在 Kotlin 中没有特殊含义。

      Unit 类型只有一个值。替换了 Java void(注意:不是 Void)。更多信息在Kotlin docs。

      Nothing 没有实例(就像Void)。它代表“一个永远不存在的价值”。在 Kotlin 中,如果您抛出错误,则它是 Nothing(参见 Kotlin docs)。

      【讨论】:

      • 而Nothing? 为空:D
      • Nothing 就像 TypeScript 中的 never
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2021-07-11
      • 1970-01-01
      • 1970-01-01
      • 2018-07-28
      • 2022-12-28
      • 2021-11-28
      • 2022-01-20
      相关资源
      最近更新 更多