Kotlin 系列文章详细计划-03-视频脚本

这是码上开学 Kotlin 系列第 3 集的视频脚本。

视频脚本不是文章结构要求,所以不属于「必读」;但建议参与文章编写的作者看一下视频脚本再去看「文章结构要求」和写文章,这样写出来的文章和视频会更容易打配合,更容易比较「搭」。

如果你想用视频脚本直接作为基准来扩展成文章,也没问题,写得好、容易读是唯一标准。

标题:Kotlin 里那些「更方便的」

脚本:

开场白

大家好,我是扔物线朱凯。上期我们讲了 Kotlin 里那些「不是那么写的」,这期再进阶一点,讲一下 Kotlin 里那些「更方便的」,那些你就算不会也能写 Kotlin,但会了能更爽的东西。

Primary Constructor

上期讲了 Kotlin 的构造函数的写法和 Java 的不同:名字统一都叫 constructor。其实 Kotlin 里还有一个更方便的写法:主构造函数 primary constructor:

  1. // 原:
  2. class User {
  3. var name: String
  4. constructor(name: String) {
  5. this.name = name
  6. }
  7. }

你给类里声明的变量赋初始值的时候,是可以直接使用主构造函数里的参数值的:

  1. // primary constructor:
  2. class User constructor(name: String) {
  3. var name: String = name
  4. }

你还可以直接在这里声明变量,这样就连赋值的代码都省了:

  1. class User constructor(var name: String) {
  2. }

哇,好方便啊!

好方便,哈?但是我得告诉你,大多数人在看见主构造函数的时候,会觉得很方便,但让他们写,他们却会发现不知道怎么写。包括很多已经写过一段时间主构造函数的人,他们并不能说出什么时候应该用它,怎么用,只能是每次用到的时候再去想,这里可不可以这么写,能不能达到我想要的效果。高效的程序员不是这么玩的同志们,我现在来给你说一下 primary constructor 主构造函数到底是怎么一个角色,你就知道什么时候应该用它、怎么用它了。

大家都知道 Java 的构造方法其实也是一种方法,只不过是用于创建对象的特殊方法。到了 Kotlin 里面,constructor 同样也是一种用于创建对象的特殊的函数——又是方法和函数,名字不同作用完全一样。函数最重要的是什么?是它的函数体,也就是它的大括号里面的代码,这里面描述了函数的行为(配图,圈住函数体)。但是 Kotlin 的主构造函数是没有函数体的。

哈?那它有什么用啊?

这就是重点:Kotlin 的 primary constructor,它的定位并不是一个函数,而是一个入口,一个接受参数的入口。接受参数干嘛呢?用来初始化对象用的。

在哪里初始化对象呢?主要有两个地方:

  • 一个是 Property 的初始赋值:

    1. class User constructor(name: String) {
    2. var name: String = name
    3. }

    你在 Property 声明的等号右边就可以用这些参数。

  • 另一个是 init 代码块:

    1. class User constructor(name: String) {
    2. var name: String
    3. init {
    4. this.name = name
    5. }
    6. }

    你在 init 代码块里也可以直接使用主构造函数里接收到的参数。

这就是主构造函数的定位:它是一个统一的参数入口,你从这里接受参数,就能在初始化过程中的不同位置都用到这些参数。

有一点要说的是,主构造函数是可选的,你可以写也可以不写,但是一旦你写了,因为对象的初始化过程是要用到它的参数的——对吧,就是刚才说的——这样你就需要在每一个次级构造函数——也就是一般的那种构造函数,它叫 secondary constructor 次级构造函数——你在每一个次级构造函数里都要调用到这个主构造函数。不然你的初始化过程是缺失参数的呀,对吧?不过这个其实到不用太操心,因为如果你不这么写,Android Studio 是会报错的。只是在你遇到这种报错的时候,你要知道是怎么回事。

另外, Kotlin 还对提供了一种快捷写法:如果你在主构造函数的参数左边加上 var 或者 val,那么这个参数除了被当做参数之外,Kotlin 还会帮你在 class 里自动创建一个和参数同名的属性(property),并且把这个参数的值作为属性的初始值。也就是说这个代码等价于这个代码,它是一种简便写法:

  1. class User constructor(var name: String) {
  2. }
  3. // 等价于:
  4. class User constructor(name: String) {
  5. var name = name
  6. }

好这个就是 Kotlin 的 primary constructor 主构造函数,那么现在回到刚才的问题:它应该怎么用?

其实说到这里可以看出,虽然主构造函数没有函数体,但是属性的初始化代码以及 init 代码块不就相当于它的函数体吗?主构造函数和它们加起来,不才是那个真正的主构造函数吗?只不过是这种由多个部分拼接成的「主构造函数」,它的写法更灵活。所以你要考虑的并不是你什么时候要为 class 创造一个主构造函数,创造一个什么样的主构造函数,而是如果这个 class 的构造函数写成主构造的形式会更简单清晰,那你就把它写成主构造的形式。像是这样:

  1. class User {
  2. var name: String
  3. constructor(name: String) {
  4. this.name = name
  5. }
  6. }
  7. class User constructor(var name: String) {
  8. }

或者更复杂点,这样:

  1. class User {
  2. var name: String
  3. var id: String
  4. constructor(name: String, id: Int) {
  5. this.name = name
  6. this.id = "id-" + id
  7. println(name + " created")
  8. }
  9. }
  10. class User constructor(var name: String, id: Int) {
  11. var id = "id-" + id
  12. init {
  13. println(name + " created")
  14. }
  15. }

然后再深入一下:如果有多个构造函数,应该把哪个写成主的呢?

刚才说过,主构造函数会参与任何一个次级构造函数创建对象的初始化过程,对吧?所以如果有多个构造函数,那你把最基本、最通用的那个写成主的就对了:

  1. // 原方案
  2. class User {
  3. var name: String
  4. var id: String
  5. constructor(name: String, id: String) {
  6. this.name = name
  7. this.id = id
  8. }
  9. constructor(person: Person) {
  10. this.name = person.name
  11. this.id = person.id
  12. }
  13. }
  14. // 方案一
  15. class User(var name: String, var id: String) {
  16. constructor(person: Person) : this(person.name, person.id) {
  17. }
  18. }
  19. // 方案二
  20. class User(person: Person) {
  21. var name: String
  22. var id: String
  23. init {
  24. name = person.name
  25. id = person.id
  26. }
  27. constructor(name: String, id: String): this(person = Person(name, id)) {
  28. }
  29. }
  30. // 当然选方案一啦!

函数简化

除了构造函数,Kotlin 的普通函数也有一些简便的语法。

比如如果函数只有一行代码,你可以把大括号去掉,把函数体加个等号直接写在右边:

  1. fun sayHi(name: String) = println("Hi " + name)

你也可以给参数配置默认值:

  1. fun sayHi(name: String = "world") = println("Hi " + name)

这样你就可以在调用函数的时候,选择性地填参数或者不填:

  1. sayHi("kaixue.io") // 打印 Hi kaixue.io
  2. sayHi() // 打印 Hi world

而且有了函数默认值的支持, Java 里的同名重载函数,在 Kotlin 里面大部分都可以写在同一个函数里了:

  1. // java
  2. public void sayHi(String name) {
  3. System.out.println("Hi " + name);
  4. }
  5. public void sayHi() {
  6. sayHi("world");
  7. }
  8. // kotlin
  9. fun sayHi(name: String = "world") = println("Hi " + name)

另外你还可以在函数里面定义函数,就像局部变量一样,这种函数叫局部函数 local function。

  1. 这个代码还没想好

字符串

Kotlin 的字符串也提供了和大多数现代语言一样的字符串模板的支持:你可以在一个字符串里插入变量:

  1. println("Hi " + name);
  2. println("Hi $name");

不过这种用法对我们 Android 工程师来说,一点都不陌生。首先,gradle 所用的语言 Groovy 本来就已经有了这种支持,而且我们在 Android 的资源文件里定义字符串不也一直在这么用吗?

  1. <string name="hi">Hi %s</string>
  1. getString(R.id.hi, "rengwuxian")

所以这个我就不多说了哈。

数组和集合

上期说了,Kotlin 自己定义了一套新的数组和集合的类型,它的目的就在于提供一套方便的操作方法。用这一套方法,你可以对数组和集合做各种便捷的整体化操作:

  1. // filter map 等代码展示

Kotlin 还额外提供了 RangeSequence 这两种新的类型。

Range 是个自动化数数工具:

  1. // Range 代码展示

Sequence 就像是一种「广度优先遍历」版本的集合类。这种数据类型可以在数据量比较大或者数据量未知的时候提供方便的流式处理方案:

  1. // Sequence 代码展示

条件控制

在条件控制方面,Kotlin 也比 Java 有一些语法上的改变和优化。其实这部分内容本来应该放在上一期《Kotlin 里 「不是那么写的」》部分,但是上期内容实在太多了,所以就把条件控制挪到这一期了。

Kotlin 的条件控制比起 Java 有一些改变,比如 Java 的 switch 在 Kotlin 里变成了 when

  1. // when 代码展示

以及判断相等的时候,Kotlin 里有两种等号:

  1. // == 和 ===

具体的可以看我们的文章来了解。

小结

今天的内容就是这些:Kotlin 里那些「更方便的」。除了开头的主构造函数,其它东西都很简单,不过还是建议你把文章看完,然后把练习题做一下,这会对你的记忆很有帮助。

这期是码上开学系列的「Kotlin 基础」部分最后一期,接下来就是一个个独立的技术点了。下期会是一个稍微有点难但也很有意思的话题:泛型。如果你喜欢我们的内容,欢迎关注收藏留言分享在看。我们下期见!

告辞!(沿用第二期的「告辞」)