Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Nothing 类型

Nothing 返回类型表示一个永远不返回的函数

通常这是一个总是抛出异常的函数。

下面是一个产生无限循环(避免使用这种情况)的函数,因为它永远不会返回,其返回类型是 Nothing

// NothingType/InfiniteLoop.kt
package nothingtype

fun infinite(): Nothing {
  while (true) {}
}

Nothing 是一个内置的 Kotlin 类型,没有实例。

一个实际的例子是内置的 TODO(),它的返回类型是 Nothing,并抛出 NotImplementedError

// NothingType/Todo.kt
package nothingtype
import atomictest.*

fun later(s: String): String = TODO("later()")

fun later2(s: String): Int = TODO()

fun main() {
  capture {
    later("Hello")
  } eq "NotImplementedError: " +
    "An operation is not implemented: later()"
  capture {
    later2("Hello!")
  } eq "NotImplementedError: " +
    "An operation is not implemented."
}

虽然 later()later2() 的返回类型都不是 Nothing,但是 TODO() 返回的是 NothingNothing 与任何类型都兼容。

later()later2() 可以成功编译。如果调用任何一个,异常会提醒您编写实现。TODO() 是一个有用的工具,用于在填充细节之前“勾勒”代码框架,以验证所有内容是否相互契合。

在下面的代码中,fail() 总是抛出 Exception,因此它的返回类型是 Nothing。请注意,调用 fail() 比显式抛出异常更易读和紧凑:

// NothingType/Fail.kt
package nothingtype
import atomictest.*

fun fail(i: Int): Nothing =
  throw Exception("fail($i)")

fun main() {
  capture {
    fail(1)
  } eq "Exception: fail(1)"
  capture {
    fail(2)
  } eq "Exception: fail(2)"
}

fail() 允许您轻松更改错误处理策略。例如,您可以更改异常类型或在抛出异常之前记录附加消息。

如果参数不是 String,则下面的代码会抛出 BadData 异常:

// NothingType/CheckObject.kt
package nothingtype
import atomictest.*

class BadData(m: String) : Exception(m)

fun checkObject(obj: Any?): String =
  if (obj is String)
    obj
  else
    throw BadData("Needs String, got $obj")

fun test(checkObj: (obj: Any?) -> String) {
  checkObj("abc") eq "abc"
  capture {
    checkObj(null)
  } eq "BadData: Needs String, got null"
  capture {
    checkObj(123)
  } eq "BadData: Needs String, got 123"
}

fun main() {
  test(::checkObject)
}

checkObject() 的返回类型是 if 表达式的返回类型。Kotlin 将 throw 视为类型 Nothing,而 Nothing 可以分配给任何类型。在 checkObject() 中,String 优先于 Nothing,因此 if 表达式的类型是 String

我们可以使用安全转换和 Elvis 运算符重写 checkObject()checkObject2() 会将 obj 转换为 String,如果可以转换,否则会抛出异常:

// NothingType/CheckObject2.kt
package nothingtype

fun failWithBadData(obj: Any?): Nothing =
  throw BadData("Needs String, got $obj")

fun checkObject2(obj: Any?): String =
  (obj as? String) ?: failWithBadData(obj)

fun main() {
  test(::checkObject2)
}

当给出一个没有附加类型信息的纯粹的 null 时,编译器会推断出一个可空的 Nothing

// NothingType/ListOfNothing.kt
import atomictest.eq

fun main() {
  val none: Nothing? = null

  var nullableString: String? = null    // [1]
  nullableString = "abc"
  nullableString = none                 // [2]
  nullableString eq null

  val nullableInt: Int? = none          // [3]
  nullableInt eq null

  val listNone: List<Nothing?> = listOf(null)
  val ints: List<Int?> = listOf(null)   // [4]
  ints eq listNone
}

您可以将 nullnone 都分配给可空类型的 varval,例如 nullableStringnullableInt。这是允许的,因为 nullnone 的类型都是 Nothing?(可空的 Nothing)。就像 Nothing 类型的表达式(例如 fail())可以被解释为“任何类型”一样,Nothing? 类型的表达式,例如 null,可以被解释为“任何可空类型”。在 [1][2][3] 中显示了不同可空类型的分配。

listNone 初始化为只包含 null 值的 List。编译器将其推断为 List<Nothing?>。因此,当您使用只包含 null 的值初始化 List 时,必须显式指定您要存储在 List 中的元素类型([4])。

Exercises and solutions can be found at www.AtomicKotlin.com.