视频先行

这是一篇视频形式的共享,假设你便利看,可以直接去看视频:

  • 哔哩哔哩:这儿
  • 抖音:这儿
  • YouTube:这儿

下面是视频内容的脚本案牍原稿共享。

视频案牍原稿

许多从 Java 转到 Kotlin 的人都会有一个疑问:为什么 Kotlin 没有沿袭 Java 的 void 关键字,而要引进这个叫 Unit 的新东西?

// Java
public void sayHello() {
  System.out.println("Hello!");
}
// Kotlin
fun sayHello(): Unit {
  println("Hello!")
}

不过这个问题一般也不会保持良久,因为就算你不明白,好像……也不影响写代码。

直到这两年,我们发现 Compose 的官方示例代码里竟然有把 Unit 填到函数参数里的状况:

LaunchedEffect(Unit) {
  xxxx
  xxxxxx
  xxx
}

我们才觉得:「啊?还能这么写?」

Unit 的实质

我们好,我是扔物线朱凯。

今日来讲一讲 Unit 这个特别的类型。

我们在刚学 Kotlin 的时分,就知道 Java 的 void 关键字在 Kotlin 里没有了,取而代之的是一个叫做 Unit 的东西:

// Java
public void sayHello() {
  System.out.println("Hello!")
}
// Kotlin
fun sayHello(): Unit {
  println("Hello!")
}

而这个 Unit,和 Java 的 void 其实是不相同的。比如 Unit 的回来值类型,我们是可以省掉掉不写的:

// Kotlin
fun sayHello() {
  println("Hello!")
}

不过省掉只是语法上的便当,实践上 Kotlin 还是会把它了解成 Unit

Unit 和 Java 的 void 真实的差异在于,void 是真的表示什么都不回来,而 Kotlin 的 Unit 却是一个真实存在的类型:

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

它是一个 object,也就是 Kotlin 里的单例类型或许说单例方针。当一个函数的回来值类型是 Unit 的时分,它是需求回来一个 Unit 类型的方针的:

// Kotlin
fun sayHello() {
  println("Hello!")
  return Unit
}

只不过因为它是个 object ,所以唯一能回来的值就是 Unit 本身。

另外,这一行 return 我们也可以省掉不写:

// Kotlin
fun sayHello() {
  println("Hello!")
}

因为就像回来值类型相同,这一行 return,Kotlin 也会帮我们自动加上:

// Kotlin
fun sayHello(): Unit {
  println("Hello!")
  return Unit
}

这两个 Unit 是不相同的,上面的是 Unit 这个类型,下面的是 Unit 这个单例方针,它俩长得相同可是是不同的东西。留意了,这个并不是 Kotlin 给 Unit 的特权,而是 object 本来就有的语法特性。你假设有需求,也可以用相同的格式来运用其他单例方针,是不会报错的:

object Rengwuxian
fun getRengwuxian(): Rengwuxian {
  return Rengwuxian
}

包含你也可以这样写:

val unit: Unit = Unit

也是相同的道理,等号左面是类型,等号右边是方针——当然这么写没什么实践效果啊,单例你就直接用就行了。

所以在结构上,Unit 并没有任何的特别之处,它就只是一个 Kotlin 的 object 算了。除了关于函数回来值类型和回来值的自动弥补之外,Kotlin 对它没有再施加任何的魔法了。它的特别之处,更多的是在于语义和用途的视点:它是个由官方规矩出来的、用于「什么也不回来」的场景的回来值类型。但这只是它被规矩的用法算了,而实质上它真就是个实实在在的类型。也就是在 Kotlin 里,并不存在真实没有回来值的函数,全部「没有回来值」的函数实质上的回来值类型都是 Unit,而回来值也都是 Unit 这个单例方针,这是 Unit 和 Java 的 void 在实质上的不同。

Unit 的价值地点

那么接下来的问题就是:这么做的含义在哪?

含义就在于,Unit 去掉了无回来值的函数的特别性,消除了有回来值和无回来值的函数的实质差异,这样许多事做起来就会更简略了。

例:有回来值的函数在重写时没有回来值

比如?

比如在 Java 里面,因为 void 并不是一种真实的类型,所以任何有回来值的方法在子类里的重写方法也都必须有回来值,而不能写成 void,不管你用不用泛型都是相同的:

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();
  }
}

Unit 为啥还能当函数参数?面向有用的 Kotlin Unit 详解

public abstract class Maker<T> {
  public abstract T make();
}
public class AppleMaker extends Maker<Apple> {
  // 合法
  Override
  public Apple make() {
    return new Apple();
  }
}
public class NewWorldMaker extends Maker<void> {
  // 不合法
  Override
  public void make() {
    world.refresh();
  }
}

Unit 为啥还能当函数参数?面向有用的 Kotlin Unit 详解

你只能去写一行 return null 来手动完成接近于「什么都不回来」的效果:

public class NewWorldMaker extends Maker {
  @Override
  public Object make() {
    world.refresh();
    return null;
  }
}

Unit 为啥还能当函数参数?面向有用的 Kotlin Unit 详解

并且假设你用的是泛型,或许还需求用一个专门的虚伪类型来让效果抵达完美:

public class NewWorldMaker extends Maker<Void> {
  @Override
  public Void make() {
    world.refresh();
    return null;
  }
}

Unit 为啥还能当函数参数?面向有用的 Kotlin Unit 详解
而在 Kotlin 里,Unit 是一种真实存在的类型,所以直接写就行了:

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()
  }
}

这就是 Unit 的去特别性——或许说通用性——所给我们带来的便当。

例:函数类型的函数参数

相同的,这种去特别性关于 Kotlin 的函数式编程也供给了便利。一个函数的函数类型的参数,在函数调用的时分填入的实参,只要符合声明里面的回来值类型,它是可以有回来值,也可以没有回来值的:

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 { "结束!" } // () -> String
runTask { 1 } // () -> Int

Java 不支持把方法作为方针来传递,所以我们无法跟 Java 做比照;但假设 Kotlin 不是像现在这样用了 Unit,而是照抄了 Java 的 void 关键字,我们就必定没方法这样写。

小结:去特别化

这就是我刚才所说的,关于无回来值的函数的「去特别化」,是 Unit 最核心的价值。它适当所以对 Java 的 void 进行了缺点的批改,让本来有的问题现在没有了。而关于实践开发,它的效果是归于润物细无声的,你不需求懂我说的这一大堆东西,也不影响你享受 Unit 的这些优点。

…………

那我出这期视频干嘛?

——开个打趣。了解各种魔法背后的实质,关于我们把握和正确地运用一门言语是很有必要的。

延伸:作为朴实的单例方针来运用

比如,知道 Unit 是什么之后,你就能了解为什么它能作为函数的参数去被运用。

Compose 里的协程函数 LaunchedEffect() 要求我们填入至少一个 key 参数,来让协程在界面状态变化时可以自动重启:

LaunchedEffect(key) {
  xxxx
  xxxxxx
  xxx
}

而假设我们没有自动重启的需求,就可以在参数里填上一个 Unit

LaunchedEffect(Unit) {
  xxxx
  xxxxxx
  xxx
}

因为 Unit 是不变的,所以把它填进参数里,这个协程就不会自动重启了。这招用着十分便利,Compose 的官方示例里也有这样的代码。不过这个和 Unit 本身的定位已经无关了,而只是是在运用它「单例」的性质。实践上,你在括号里把它换成任何的常量,效果都是完全相同的,比如 true、比如 false、比如 1、比如 0、比如 你好,都是可以的。所以假设你什么时分想「随意拿个方针过来」,或许「随意拿个单例方针过来」,也可以运用 Unit,它和你自己创建一个 object 然后去运用,效果是相同的。

总结

好,这就是 Kotlin 的 Unit,希望这个视频可以协助你更好地了解和运用它。下期我会讲 Kotlin 里另一个特别的类型:Nothing。注重我,了解更多 Android 开发的常识和技能。我是扔物线,我欠好你比凹凸,我只助你成长。我们下期见!