kotlin中的Unit和Nothing 关键字

473 阅读5分钟

让我们先从 kotlin 的类型继承关系开始:众所周知,kotlin 中所有东西都有类型,对象、函数等等,就连 Unit,Nothing 也有对应的类型。我们来看一下kotlin 中的类型层次结构。

从顶部开始

所有类型的 Kotlin 对象都被组织成子类型/超类型关系的层次结构。该层次结构的“顶部”是抽象类Any。例如,String 和 Int 类型都是的子类型Any。

graph BT
String-->Any
Int-->Any

这里的 Any 相当于 java 中的 Object,同样的,如果我们声明一个类,没有显式指定继承自哪个类,那这个类就是 Any 的直接子类。 如果该类指定了父类,那么 Any 会是该类的最终父类(祖先)

open class Person(val name: String)
class Student(name:String):Person(name)
class Teacher(name:String):Person(name)
graph BT
Person --> Any
Student-->Person
Teacher-->Person

如果一个类实现了多个接口,那么它就会有多个直接父类

interface Run
interface Fly
class Bird : Run,Fly
graph BT
Run --> Any
Fly --> Any
Bird-->Run
Bird-->Fly

Kotlin 类型检查器强制执行子类型/父类型关系,我们可以将子类型存储到超类型变量中,反过来则不行,这和 java 是一样的逻辑。 另外,kotlin 中还有可空类型,上面我们提到的类型都是非空类型,可空类型只是在可空类型后面加了一个 ?,

val name :String =null //错误:Null can not be a value of a non-null type String
val name :String? =null //正确
var s: String? = null
var s2 = "a"
s = s2
s = null
s2 = s//错误:Type mismatch.Required:String, Found:String?

从这个角度来看,我们可以认为(仅仅是可以认为)非空类型是对应可空类型的子类,因为非空类型可以赋值给对应的可空类型,反之则不行。

graph BT
Any --> Any?
String? --> Any?
String-->Any
String-->String?

Unit

Kotlin中的Unit即相当于Java中的void关键字,用于表示返回空结果的函数。但这里有一些不一样的地方,当 Unit 用于函数返回值时,是可以省略不写的,但kotlin 还是会认为返回了 Unit。其次,Unit 是一个真实存在的类型,并且是一个单例的。


fun unitFun() {
    println("unitFun")
}
fun unitFun1(): Unit {
    println("unitFun1")
}
fun unitFun2(): Unit {
    println("unitFun2")
    return Unit
}
fun unitFun3() {
    println("unitFun3")
    return
}
fun unitFun4() {
    println("unitFun3")
    return Unit
}

public object Unit {
    override fun toString() = "kotlin.Unit"
}

我们发现,上面这几种写法,效果是一样的。但这里有一个点需要注意一下: 对于unitFun2()这个函数,跟在函数名后面的Unit表示返回值类型,而函数体里面的return Unit中的 Unit 是一个单例对象。虽然看起来什么也没有返回,但它确实返回的一个单例对象。 回到我们上面提到的可空类型上,一个比较边缘的 case:Unit?类型

var nullableUnit:Unit? = null

这个东西我从来没有使用过,是 kotlin 类型一致性的结果

graph BT
Any --> Any?
Unit? --> Any?
Unit-->Any
Unit-->Unit?

那 Unit 这个东西有什么用呢?为啥不沿用 java 中的 void ? 扔物线大佬在他的文章中给出了具体的回答:去特殊化,Unit去掉了无返回值的函数的特殊性,消除了有返回值和无返回值的函数的本质区别,这样很多事做起来就会更简单了。 例:有返回值的函数在重写时没有返回值

public abstract class Maker {
  public abstract Object make();
}

public class AppleMaker extends Maker {
  // 合法
  @Override
  public Apple make() {
    return new Apple();
  }
}

public class NewWorldMaker extends Maker {
  // 非法
  @Override
  public void make() {
    world.refresh();
  }
}
public class NewWorldMaker extends Maker {
  @Override
  public Object make() {
    world.refresh();
    return null;//只能去写一行 return null 来手动实现接近于「什么都不返回」的效果
  }
}
abstract class Maker {
  abstract fun make(): Any
}

class AppleMaker : Maker() {
  override fun make(): Apple {
    return Apple()
  }
}

class NewWorldMaker : Maker() {
  override fun make() {
    world.refresh()
  }
}

abstract class Maker<T> {
  abstract fun make(): T
}

class AppleMaker : Maker<Apple>() {
  override fun make(): Apple {
    return Apple()
  }
}

class NewWorldMaker : Maker<Unit>() {
  override fun make() {
    world.refresh()
  }
}

比如函数类型的函数参数

fun runTask(task: () -> Any) {
  when (val result = task()) {
    Unit -> println("result is Unit")
    String -> println("result is a String: $result")
    else -> println("result is an unknown type")
  }
}

...

runTask { } // () -> Unit
runTask { println("完成!") } // () -> String
runTask { 1 } // () -> Int

Nothing

这里抛出一个引子:

fun fail() {
    throw RuntimeException("Something went wrong")
}

这个方法的返回值是什么?它真的有返回值么?如果有,是什么类型? 实际上这个方法的返回值类型是Nothing kotlin中有这么一个方法:TODO,注意,这里的 TODO 是一个方法,而不是//todo这一个

public inline fun TODO(): Nothing = throw NotImplementedError()

我们来看一下 Nothing 定义

/**
 * Nothing has no instances. You can use Nothing to represent "a value that never exists": for example,
 * if a function has the return type of Nothing, it means that it never returns (always throws an exception).
 */
public class Nothing private constructor()

这个类无法创建出任何实例,所以所有 Nothing 类型的变量或者函数,都找不到可用的值。那这个东西有啥用?正如它的注释所说, 它可以作为函数「永不返回」的提示,也就是总是抛出异常。 那这个东西有啥用? 第一个作用就是作为函数「永不返回」的提示 第二个作用就是作为泛型对象的临时空白填充,比如

val emptyList: List<Nothing> = listOf()
var apples: List<Apple> = emptyList
var users: List<User> = emptyList
var phones: List<Phone> = emptyList
var images: List<Image> = emptyList

既省事,又省内存。当然也可以用在 Set、Map 中 第三个作用:语法的完整化 Nothing 的「是所有类型的子类型」这个特点,还帮助了 Kotlin 语法的完整化。在 Kotlin 的下层逻辑里,throw 这个关键字是有返回值的,它的返回值类型就是 Nothing。虽然说由于抛异常这件事已经跳出了程序的正常逻辑,所以 throw 返回不返回值、返回值类型是不是 Nothing 对于它本身都不重要,但它让类似 TODO()函数的写法成为了合法的。

graph BT
String --> Any
Int --> Any
Unit --> Any
Person --> Any
Nothing --> Person
Nothing --> Unit
Nothing --> Int
Nothing --> String

参考

A Whirlwind Tour of the Kotlin Type Hierarchy
Unit 为啥还能当函数参数?面向实用的 Kotlin Unit 详解
这玩意真的有用吗?对,是的!Kotlin 的 Nothing 详解