Kotlin-空虚vs.单位vs.无

时间:2019-05-02 12:59:18

标签: kotlin

科特林有三种性质非常相似的类型:

  • Void
  • Unit
  • Nothing

似乎他们在犯JavaScript错误:

  • null
  • undefined
  • void(0)

假设它们没有犯同样的错误,它们的作用是什么?它们有什么不同?

3 个答案:

答案 0 :(得分:10)

Void类型来自Java。除非您正在使用使用它的Java库,否则通常不会从Kotlin使用它。

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

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

由于Nothing没有值,因此Nothing?实际上是在Kotlin中仅捕获null值的类型。

答案 1 :(得分:4)

Void是普通的Java类,在Kotlin中没有特殊含义。

Unit替换了Java void(注意不是Void

Nothing是一个不存在的值。如果抛出错误,则为Nothing。更多信息here

答案 2 :(得分:4)


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表示。使用类的原因是,通过使类成为类型层次结构的一部分,可以使类型系统更加一致。

例如,假设我们有一个名为interface的通用Worker<T>,它可以执行一些工作。该接口的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中所有类型的子类型,就像Any是所有类型的超类型一样。因此,类型Nothing的值(永不存在)可分配给所有类型的变量,例如:

val user: User = request.user ?: error("User not found")

在这里,如果error()user,则使用elvis运算符(null)调用上面定义的?:函数。 error()函数返回类型Nothing的值,但是可以将其分配给类型User的变量,因为NothingUser的子类型,就像它是任何其他类型的子类型。编译器允许这样做,因为它知道error()函数永远不会返回值,因此没有危害。

类似地,您可以从具有任何其他返回类型的函数中返回Nothing

fun getUser(request: Request): User {
    return request.user ?: error("User not found")
}

此处,即使getUser()函数声明为返回User,但如果Nothinguser,它也可能返回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或为空的不确定性,并使意图更清晰。它还删除了空检查,因为空对象上的函数为空,它们将被调用,但它们为 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>和... List<AllTypes>以及在多个地方。由于每次都返回相同的对象,因此节省了对象创建和内存分配的成本。


Void


Void用于扩展Java中的泛型

Void类来自java.lang包,而UnitNothing来自kotlin包。 Void不能在Kotlin中使用。 Kotlin以Unit的形式有自己的课程。

Void在Java中用于扩展通用接口,例如我们为Worker编写的Unit接口示例,其中我们必须返回一个值。因此,要将Kotlin代码转换为Java,我们可以像在Void示例中使用Unit一样使用Worker,并按如下所示用Java重写代码:

interface Worker<T> {
    T doWork();
}

class LogWorker implements Worker<Void> {
    @Override public Void doWork() {
        // Do the logging
        return null;
    }
}

请注意,使用Void时,我们必须使用Void作为返回类型(不能跳过),并且需要编写return语句,而对于{{1 }}我们都可以跳过。这是避免在Kotlin代码中使用Unit的另一个原因。


结论

因此,我认为VoidUnit并不是Kotlin设计师的错误,也没有像Nothingnullundefined Javascript。 void(0)Unit使函数式编程变得轻而易举,同时还提供了上述其他有用的功能。它们在其他函数式编程语言中也很常见。

就是这样!希望有帮助。