Kotlin 是一门 JVM 语言,它诞生于2011年,2016年发布1.0版本,2017年被谷歌确立为Android一级开发语言,主要通过 Kotlin 编译器,将 kt 文件编译成 class 文件,得以让 JVM 进行执行。

编程之本:变量和函数

变量

val (value 的简写)用来声明一个不可变的变量,这种变量在初始化赋值之后就再也不能重新赋值,对应 Java 中的 final 变量。
var (variable 的简写)用来声明一个可变的变量,这种变量在初始化赋值之后仍然可以被再次赋值,对应 Java 中的非 final 变量。

在 Java 中最常见的变量大部分都是非 final 变量,但是这并不是一个良好的编程习惯。

当项目变得比较复杂的时候,开发人员越来越多,你永远不知道一个变量会在什么时候被谁给修改了,即使它本身不应该被修改,这将会导致一些难以排查的问题。所以,请永远先用 val 声明你的变量,直到它无法满足你的需求,再改用 var 声明。

Kotlin 支持类型推导,例如下面的例子:

  1. val a = 10 // 变量a被自动推导为整型
  2. val b = "这是个字符串" // 变量b被推导为字符串类型
  3. fun function1(): String {
  4. return "这是个返回值字符串"
  5. }
  6. val c = function1() // 变量c被推导为字符串类型

但是类型推导在一些场景下是无法生效的,比如延迟初始化时,所以Kotlin为了这些场景采用了后置显式声明类型的方式,例如下面的例子:

  1. val a: Int = 10
  2. val b: String = "这是个字符串"
  3. lateinit var c: String // 延迟初始化字符串变量c

Kotlin 中没有基本数据类型,全部都是对象数据类型。下表展示了Java和Kotlin中的数据类型对照关系:

Java基本数据类型 Kotlin对象数据类型 数据类型说明
int Int 整型
long Long 长整型
short Short 短整型
float Float 单精度浮点型
double Double 双精度浮点型
char Char 字符型
boolean Boolean 布尔型
byte Byte 字节型

函数

Kotlin 中采用 fun 关键字(function 的简称)来声明函数,语法规则如下:

  1. fun methodName(param1: Int, param2: Int): Int {
  2. // do something
  3. return 0
  4. }

紧跟在 fun 后面的是函数名,函数名一般采用驼峰命名法。函数名后面用一对括号来声明函数的入参,每个参数都保持 参数变量名: 数据类型 的形式,并使用逗号隔开,若不想接收任何参数,则使用空括号就行了。最后用 : Int 来声明函数执行完毕后返回值的类型,如果不需要任何返回值,可以不写。最后两个大括号中间的就是函数体了。

当函数体比较简单时,可以直接使用 = 将函数体的执行结果返回出去。例如下面的例子:

  1. fun largerNumber(num1: Int, num2: Int): Int = max(num1. num2)

利用 Kotlin 类型推导的特性,还可以省略 : Int 的返回值声明,直接简写为:

  1. fun largerNumber(num1: Int, num2: Int) = max(num1. num2)

程序的逻辑控制

if 条件语句

Kotlin 的 if 条件语句与 Java 的基本一致,但是他有一个额外的功能——它是有返回值的,返回值就是 if 语句每个条件中最后一行代码的返回值。具体的代码如下:

  1. fun largerNumber(num1: Int, num2: Int) = if (num1 > num2) {
  2. num1
  3. } else {
  4. num2
  5. }

当条件语句的代码块比较简单,只有一行时,还可以省略花括号。上面的代码也可以简写成:

  1. fun largerNumber(num1: Int, num2: Int) = if (num1 > num2) num1 else num2

注意: Kotlin 中没有 Java 中经常使用的「三元表达式」语法,所以需要使用三元表达式的时候需要我们用 if-else 来代替。

when 条件语句

Kotlin 的 when 语句可以看做是 Java 语法中的 switch 语句的加强版。

在使用 Java 的 switch 语句时,我们只能传入字符串、整型或短于整型的变量作为条件;其次,每个 case 最后都要加上 break 关键字,否则执行完当前的 case 还会执行下一个 case ,这个特性会导致很多bug。

Kotlin 的 when 语句不仅解决了上述的问题,还加入了一些新的特性。下面是它的语法结构:

  1. fun getScore(name: String) = when(name) {
  2. "Tom" -> 86
  3. "Jim" -> 95
  4. else -> {
  5. println("查无此人")
  6. 0
  7. }
  8. }

when 语句允许传入一个任意类型的参数,然后在 when 的结构体中定义一系列的条件,格式为 匹配值 -> { 执行逻辑 } 。执行逻辑的最后一行可以作为返回值,当执行逻辑只有一行时可以省略花括号。

除了精确匹配以外,when 语句还允许进行类型匹配。例如:

  1. fun checkNumber(num: Number) {
  2. is Int -> println("number is Int")
  3. is Double -> println("number is Double")
  4. else -> println("unsupported type")
  5. }

上述的代码中,关键字 is 是类型匹配的核心。它相当于 Java 中的 instanceof 关键字。

其实when语句还可以不带任何参数,虽然这种用法不太常用,但是有时候却更加灵活。例如下面的例子:

  1. fun getScore(name: String) = when {
  2. name.startsWith("Tom") -> 86
  3. name == "Jim" -> 95
  4. else -> {
  5. println("查无此人")
  6. 0
  7. }
  8. }

循环语句

Kotlin 的 while 循环和 Java 的语法基本一致,但是 for 循环则被改动较大。 Java 中最常用的 for-i 循环被舍弃了, for-each 循环在 Kotlin 中则变成了 for-in 循环,得到了大幅度的加强。

in 关键字搭配的主要是几个表示区间的关键字。例如:

  1. for (i in 0..10) {
  2. // 表示 [0, 10] 闭区间,步长为 1 的遍历
  3. }
  4. for (i in 0 until 10) {
  5. // 表示 [0, 10) 左闭右开区间,步长为 1 的遍历
  6. }
  7. for (i in 0 until 10 step 2) {
  8. // 表示 [0, 10) 左闭右开区间,步长为 2 的遍历
  9. }
  10. for (i in 10 downTo 1) {
  11. // 表示 [10, 1] 的降序闭区间,步长为 1 的遍历
  12. }

除去对区间进行遍历以外, for-in 循环还可以对数组或区间进行遍历。

面向对象编程

声明一个类:

  1. class Person {
  2. var name = ""
  3. var age = 0
  4. fun eat() {
  5. println("$name is eating")
  6. }
  7. }
  8. // 使用时
  9. val person = Person()
  10. person.name = "Jack"
  11. person.age = 22
  12. person.eat()

可以看到,Kotlin的类在实例化时,是不需要 new 关键字的,而是直接调用构造方法。

继承父类

  1. open class Person {
  2. var name = ""
  3. var age = 0
  4. fun eat() {
  5. println("$name is eating")
  6. }
  7. }
  8. class Student: Person() {
  9. var studyNo = 0
  10. var score = 0
  11. fun study() {
  12. println("$name is studying")
  13. }
  14. }

一个类如果可以被继承,那么必须要加上 open 关键字。子类继承父类时,使用 : 后面跟上父类的构造函数。

构造函数

Kotlin中构造函数分为了主构造函数和次构造函数。

主构造函数

主构造函数将会是你最常用的构造函数,每个类默认都会有一个不带参数的主构造函数,当然你也可以显式的给它指明参数。主构造函数的特点是没有函数体,直接定义在类名的后面即可。比如下面的写法:

  1. class Student(val studyNo: Int, val score: Int): Person() {
  2. // ...
  3. }

主构造函数没有函数体,那如果想要在构造方法中处理一段逻辑怎么办?可以写在 init 代码块中。

  1. class Student(val studyNo: Int, val score: Int): Person() {
  2. init {
  3. println("student whose No. is $studyNo got score: $score")
  4. }
  5. }

为什么继承时,子类的主构造函数后面要跟上 :父类构造函数 呢?因为Java要求子类构造函数中必须调用父类的某一个构造函数。

如果父类没有无参的构造函数,那么子类应该在构造函数中收集相应的参数,但是在声明时不应该加上 val 关键字。代码如下:

  1. open class Person(val name: String, val age: Int) {
  2. // ...
  3. }
  4. class Student(
  5. val studyNo: Int, // 加上 val/var 声明,表面这是一个类变量
  6. val score: Int,
  7. name: String, // 没有 val/var 声明,表明这只是个构造函数入参,是个局部变量
  8. age: Int
  9. ): Person(name, age) {
  10. // ...
  11. }

次构造函数

其实大部分场景下是用不到次构造函数的,因为Kotlin提供了默认参数的功能,基本上可以代替次构造函数的作用。

在Kotlin中,一个类只能有一个主构造函数,但是可以有多个次构造函数。当一个类既有主构造函数也有次构造函数的时候,所有的次构造函数都必须调用主构造函数(包括间接调用)。

次构造函数是通过 constructor 关键字来定义的,并且具有函数体。例如下面的例子:

  1. class Student(
  2. val studyNo: Int,
  3. val score: Int,
  4. name: String,
  5. age: Int
  6. ): Person(name, age) {
  7. constructor(name: String, age: Int): this("", 0, name, age) {
  8. // ...
  9. }
  10. constructor(): this("", 0) {
  11. // ...
  12. }
  13. }

这里我们定义了两个次构造函数:第一个接收 nameage 参数,然后它又通过 this 关键字调用了主构造函数,并将 studyNoscore 这两个参数赋值成初始值;第二个次构造函数是无参构造函数,他通过 this 关键字调用我们刚刚定义的第一个次构造函数,将 nameage 也设置成初始值,这相当于间接调用了主构造函数。

特殊情况

一个类可以只有次构造函数,没有主构造函数吗?在Kotlin中是可以的,例如下面的例子:

  1. class Student: Person {
  2. constructor(name: String, age: Int): super(name, age) {
  3. // ...
  4. }
  5. }

可以看到,由于 Student 类没有主构造函数,所以在继承时,没有直接调用 Person 类的无参构造函数,所以 Person 后面没有括号。然后在次要构造函数的声明时,因为自己没有主构造函数,所以只能调用父类的构造函数,这里需要使用 super 关键字来调用父类的构造函数。

接口

Kotlin与Java一致,都采用了单继承多实现的方式。而Java中继承使用关键字 extends ,实现使用关键字 implement ,Kotlin中则统一使用 : 关键字来声明, 多个接口之间使用逗号来分隔。另外,Kotlin 实现接口时后面不需要加上括号,因为接口没有构造函数可以调用。例如:

  1. interface Study {
  2. fun readBooks()
  3. fun doHomework()
  4. }
  5. class Student(name: String, age: Int): Person(name, age), Study {
  6. override fun readBooks() {
  7. // ...
  8. }
  9. override fun doHomework() {
  10. // ...
  11. }
  12. }

Kotlin 的接口中的方法允许具有默认实现,但是不用像 Java 一样加上 default 关键字,而是像定义一个普通的类方法一样去定义即可。

可见性修饰符

image.png

数据类和单例类