这是码上开学 Kotlin 系列第一集的视频脚本。
视频脚本不是文章结构要求,所以不属于「必读」;但建议参与文章编写的作者看一下视频脚本再去看「文章结构要求」和写文章,这样写出来的文章和视频会更容易打配合,更容易比较「搭」。
呼。我来了(笑)。
大家好,我是扔物线朱凯。从今天开始,我们的码上开学 Kotlin 上手指南系列正式开始更新了。我们会用一系列的视频 + 文章 + 练习题,帮助各位想学 Kotlin 的 Android 工程师轻松地上手 Kotlin。今天就是第一期:Kotlin 的变量、函数和类型。不过在开始之前,我要先跟大家说明一下:
- 首先,这是一份上手指南,而不是技术文档。所以:
- 我们会带着你一步步地、有节奏地学习,让你轻松愉快地学会 Kotlin;
- 但这里不会有完整的 API 清单。如果你想查看 API,可以去看 Kotlin 官方文档。
- 然后,这虽然只是一份「上手」指南,我们也不会刻意展示过于深入的内容,但所有你需要了解的技术细节,一个都不会少。
- 还有就是,我们针对的是 Android 工程师,因此所有的视频和文章讲解以及示例代码,全都会以 Android 开发场景为基础,所用的开发环境也是 Android Studio。如果这份指南能顺便让一些其他领域的 Java 开发者获益当然更好,但仅仅是顺便😂。
Kotlin 里声明一个变量(field、local variable)的方式就和 Java 不一样,在 Kotlin 里要这么写:
但如果真的这么写,IDE 会报错:
这个提示是在说,属性需要在声明的同时初始化,除非你把它声明成抽象的。
视频额外字幕:「Java 的字段(field)在 Kotlin 里面被隐藏了,取而代之的是属性(property)」
哎,变量还能抽象的?对,这是 Kotlin 的功能,不过这里先不理它,后面会讲到。
-
为什么要初始化?因为 Kotlin 的变量没有默认值的,这点不像 Java,Java 的字段(field)有默认值,引用类型的默认 null,int 类型的默认 0,这些 Kotlin 没有。
不过其实,Java 也只是字段有默认值,局部变量也是没有默认值的:
-
好拐回来说,需要初始化,那就给它初始化呗,赋值一个 null 给他:
-
啊又不行,告诉我需要赋一个非空的值给它才行,怎么办?
-
那就给它个非空的值呗:
-
哎不对啊,
findViewById()
现在还不能用的,必须写在onCreate()
里面或者之后。那那那,怎么办? -
「哎好麻烦啊,不学了,我还是用 Java 吧。」
-
其实这都是 Kotlin 的空安全设计相关的内容。很多人尝试上手 Kotlin 之后快速放弃,就是因为搞不明白它的空安全设计,导致代码各种拒绝编译,最终光速放弃。所以咱先别猴急,我先用几十秒钟来给你讲一下 Kotlin 的空安全。
空安全?简单来说就是通过 IDE 的提示来避免调用 null 对象,从而避免 NullPointerException。其实 androidx (support lib) 就有的,用一个注解就可以标记变量是否可能为空,然后 IDE 会帮助检测和提示:
而到了 Kotlin 这里,就有了语言级别的默认支持,而且从警告变成了报错(编译失败):
-
在 Kotlin 里面,所有的变量都默认是不允许为空的,如果你给它赋值 null,就会被报错,像上面那样。
这种有点强硬的要求,其实是很合理的:既然你声明了一个变量,就是要使用它对吧?那你把它赋值为 null 干嘛?要尽量让它有可用的值啊。
Java 在这方面很宽松,我们成了习惯,但 Kotlin 更强的限制其实在你熟悉了之后,是会减少很多运行时的问题的。
-
另外……这个 View 其实在实际场景中我们是不会让它为空的对吧?这种「我很确定我用的时候绝对不为空,但第一时间我没法给它赋值」的场景,Kotlin 给了我们一个选项:lateinit:
-
这个
lateinit
的意思很简单:我没法第一时间就初始化,但我肯定会在使用它之前完成初始化的。 -
而它的作用也很直接:让 IDE 不要对这个变量检查初始化和报错。换句话说,加了这个关键字,这个变量的初始化就全靠你自己了,编译器不帮你了。
-
不过,还是有些场景,变量的值真的无法保证空与否,比如你要从服务器取一个 JSON 数据,并把它解析成一个
User
对象:闪现半秒钟,切屏
哦串片了,这是后面的内容。有的时候你从服务器取一个
User
对象,服务器传给你的数据未必有name
。这个时候,空值就是有意义的。对于这些可以为空值的变量,你可以在类型右边加一个 ? 号,解除它的非空限制:
也就是,加了问号之后,一个 Kotlin 变量就像 Java 变量一样没有非空的限制,自由自在了。你除了在初始化的时候可以给他设置为空值,在代码里的任何地方也都可以。不过,可空类型的变量会有新的问题:
-
由于对空引用的调用会导致空指针异常,所以 Kotlin 在可空变量直接调用的时候 IDE 会报错:
-
「可能为空」的变量,Kotlin 不允许用。那怎么办?用之前检查一下吧:
-
哎?这怎么还报错?这个报错的意思是你检查了非空也不能保证下面调用的时候就是非空(因为多线程情况下,其他线程可能把它再改成空的)。不过 Kotlin 里其实不是这么玩的,而是用
?
:这个写法同样会对变量做一次非空确认之后再调用方法,不过这是 Kotlin 的写法,它可以做到线程安全。
-
也就是说,如果你要在 Kotlin 里使用一个可能为空的变量,代码大概是这样的:
-
另外还有一种双感叹号的用法,我在视频里就不介绍了,你们看文章吧。
-
-
这就是 Kotlin 的变量以及空安全设计。其实它的声明跟 Java 虽然完全不一样,但是学习起来完全没难度的,因为只是一个风格而已。但很多人在上手的时候都被变量声明搞蒙,原因就是 Kotlin 的空安全设计所导致的这些报错:
- 变量需要手动初始化,所以不初始化的话报错;
- 变量默认非空,所以初始化赋值 null 的话也报错;
- 变量用 ? 设置为可空的时候,使用的时候因为「可能为空」又报错。
关于空安全,最重要的是记住一点:所谓「可空不可空」,关注的全都是使用的时候,即「这个变量在使用时是否可能为空」。如果一个变量从头到尾只有赋值没有调用,要它有什么用呢?
哔——
另外 Kotlin 有个很方便的地方是,如果你在声明的时候就赋值,那不写变量类型也行:
var name: String = "rengwuxian"
🔽
var name = "rengwuxian"
这个叫做「类型推断」,它跟动态类型是不一样的:
动态类型是变量的类型在运行时可以改变;而类型推断是你在代码里不用写变量类型,编译器在编译的时候会帮你补上。
哦对了,变量除了 var 打头,还可以写成 val 打头的:
val size = 18
val 是 Kotlin 在 Java 的「变量」类型之外,又增加的一种变量类型:只读变量。它只能赋值一次,不能修改。var 是 variable 的缩写,val 是 value 的缩写。
在 Java 里,设置只读变量用的是 final
关键字,不过 Kotlin 的 val
和 Java 的 final
有一点点不一样。那不一样?下期讲。
-
除了变量,Kotlin 的函数声明方式也和 Java 的方法不一样了。——嗯 Java 的方法(method)到了 Kotlin 就叫函数(function)了。这俩的区别就是没有区别。
视频额外字幕:其实是有的,但是在 2019 年这个时代,大家已经把这两个词混用了。
-
另外,函数的参数也有可空的控制:
String myName : String? = "rengwuxian" // 可空变量 fun cook(name: String) : Food {} // 不可空参数 cook(myName) // 报错 String myName : String? = "rengwuxian" // 可空变量 fun cook(name: String?) : Food {} // 可空参数 cook(myName) // 可以 String myName : String = "rengwuxian" // 不可空变量 fun cook(name: String) : Food {} // 不可空参数 cook(myName) // 可以
-
函数如果没有返回值,Java 里是 void,Kotlin 是
Unit
:fun suicide() : Unit {} fun suicide() {} // Unit 返回类型可以省略
-
讲到函数,Kotlin 和 Groovy 一样,是有默认的 getter/setter 支持的。简单说就是当你取变量的值的时候以及给变量赋值的时候,实际上是调用了它的 get 和 set 方法:
name = "rengwuxian" // 你写的代码 setName("rengwuxian") // 实际上发生的代码 println(name) // 你写的代码 println(getName()) // 实际上发生的代码
这有什么用?
-
主要作用在于:所有的属性的读写都被自动加了一个钩子,你可以为每个变量设置钩子代码:
setName(newName : String) { name = newName log("New name logged: " + name) }
-
另外你也可以利用这个钩子,去修改一个变量的赋值和取值的具体手段:
val size = 0 fun getSize() : Int { return items.size }
所以「只读变量」也未必每次取得的值都一样的,对吧?
-
不过其实 Kotlin 的 getter / setter 是有专属写法的。
再说一下 Kotlin 的基本类型,也就是
int
float
那些东西。在 Kotlin 里面,不再使用小写的int
float
这些基本类型,也不再使用首字母大写的Integer
Float
这些类,而是统一换成了Int
Float
这样的几个类,这几个类是 Java 没有的,是 Kotlin 自己造的。在 Kotlin 里,你写一个
1
,它的类型是大Int
。也就是说,int
float
这些基本类型,在 Kotlin 里面,被抛弃了,Kotlin 里不再有基本类型了。在最终从 Kotlin 代码编译出的字节码当然会有 JVM 的基本类型,但在语言层面,Kotlin 是把 Java 的基本类型完全阉割掉了。这个……就很自然地会给我们一种不安全感:没了int
,会不会有什么暂时想不到的麻烦?有,但都很好解决,等你遇到了再说完全没问题。
除此刚才说到的这些,Kotlin 的类的写法:
class MainActivity : AppCompatActivity {}
只展现半秒。
构造方法:
class MainActivity : AppCompatActivty { constructor()
-
}
> 也只展现半秒。
以及继承:
> 图片,同样是半秒。
都跟 Java 有不同。
这些东西,以及刚才说的变量和函数的细节,文章里都写得非常清楚。如果你打算学习 Kotlin,或者已经上手了 Kotlin 但一直没有找到合适的机会整体学习一下,赶快打开文章看一下吧,大概会花掉你几十分钟的时间。看完记得马上把文章末尾附加的练习题做了,今天的内容就掌握得差不多了。更多的东西:单例、扩展函数、数据类、内联函数、高阶函数、委托、标准函数、Kotlin 的泛型、反射和注解、协程,都会在接下来的一段时间连续释放,所以赶快关注收藏留言分享,码上开学要开始疯狂输出了!