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 支持类型推导,例如下面的例子:
val a = 10 // 变量a被自动推导为整型val b = "这是个字符串" // 变量b被推导为字符串类型fun function1(): String {return "这是个返回值字符串"}val c = function1() // 变量c被推导为字符串类型
但是类型推导在一些场景下是无法生效的,比如延迟初始化时,所以Kotlin为了这些场景采用了后置显式声明类型的方式,例如下面的例子:
val a: Int = 10val b: String = "这是个字符串"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 的简称)来声明函数,语法规则如下:
fun methodName(param1: Int, param2: Int): Int {// do somethingreturn 0}
紧跟在 fun 后面的是函数名,函数名一般采用驼峰命名法。函数名后面用一对括号来声明函数的入参,每个参数都保持 参数变量名: 数据类型 的形式,并使用逗号隔开,若不想接收任何参数,则使用空括号就行了。最后用 : Int 来声明函数执行完毕后返回值的类型,如果不需要任何返回值,可以不写。最后两个大括号中间的就是函数体了。
当函数体比较简单时,可以直接使用 = 将函数体的执行结果返回出去。例如下面的例子:
fun largerNumber(num1: Int, num2: Int): Int = max(num1. num2)
利用 Kotlin 类型推导的特性,还可以省略 : Int 的返回值声明,直接简写为:
fun largerNumber(num1: Int, num2: Int) = max(num1. num2)
程序的逻辑控制
if 条件语句
Kotlin 的 if 条件语句与 Java 的基本一致,但是他有一个额外的功能——它是有返回值的,返回值就是 if 语句每个条件中最后一行代码的返回值。具体的代码如下:
fun largerNumber(num1: Int, num2: Int) = if (num1 > num2) {num1} else {num2}
当条件语句的代码块比较简单,只有一行时,还可以省略花括号。上面的代码也可以简写成:
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 语句不仅解决了上述的问题,还加入了一些新的特性。下面是它的语法结构:
fun getScore(name: String) = when(name) {"Tom" -> 86"Jim" -> 95else -> {println("查无此人")0}}
when 语句允许传入一个任意类型的参数,然后在 when 的结构体中定义一系列的条件,格式为 匹配值 -> { 执行逻辑 } 。执行逻辑的最后一行可以作为返回值,当执行逻辑只有一行时可以省略花括号。
除了精确匹配以外,when 语句还允许进行类型匹配。例如:
fun checkNumber(num: Number) {is Int -> println("number is Int")is Double -> println("number is Double")else -> println("unsupported type")}
上述的代码中,关键字 is 是类型匹配的核心。它相当于 Java 中的 instanceof 关键字。
其实when语句还可以不带任何参数,虽然这种用法不太常用,但是有时候却更加灵活。例如下面的例子:
fun getScore(name: String) = when {name.startsWith("Tom") -> 86name == "Jim" -> 95else -> {println("查无此人")0}}
循环语句
Kotlin 的 while 循环和 Java 的语法基本一致,但是 for 循环则被改动较大。 Java 中最常用的 for-i 循环被舍弃了, for-each 循环在 Kotlin 中则变成了 for-in 循环,得到了大幅度的加强。
与 in 关键字搭配的主要是几个表示区间的关键字。例如:
for (i in 0..10) {// 表示 [0, 10] 闭区间,步长为 1 的遍历}for (i in 0 until 10) {// 表示 [0, 10) 左闭右开区间,步长为 1 的遍历}for (i in 0 until 10 step 2) {// 表示 [0, 10) 左闭右开区间,步长为 2 的遍历}for (i in 10 downTo 1) {// 表示 [10, 1] 的降序闭区间,步长为 1 的遍历}
除去对区间进行遍历以外, for-in 循环还可以对数组或区间进行遍历。
面向对象编程
声明一个类:
class Person {var name = ""var age = 0fun eat() {println("$name is eating")}}// 使用时val person = Person()person.name = "Jack"person.age = 22person.eat()
可以看到,Kotlin的类在实例化时,是不需要 new 关键字的,而是直接调用构造方法。
继承父类
open class Person {var name = ""var age = 0fun eat() {println("$name is eating")}}class Student: Person() {var studyNo = 0var score = 0fun study() {println("$name is studying")}}
一个类如果可以被继承,那么必须要加上 open 关键字。子类继承父类时,使用 : 后面跟上父类的构造函数。
构造函数
主构造函数
主构造函数将会是你最常用的构造函数,每个类默认都会有一个不带参数的主构造函数,当然你也可以显式的给它指明参数。主构造函数的特点是没有函数体,直接定义在类名的后面即可。比如下面的写法:
class Student(val studyNo: Int, val score: Int): Person() {// ...}
主构造函数没有函数体,那如果想要在构造方法中处理一段逻辑怎么办?可以写在 init 代码块中。
class Student(val studyNo: Int, val score: Int): Person() {init {println("student whose No. is $studyNo got score: $score")}}
为什么继承时,子类的主构造函数后面要跟上 :父类构造函数 呢?因为Java要求子类构造函数中必须调用父类的某一个构造函数。
如果父类没有无参的构造函数,那么子类应该在构造函数中收集相应的参数,但是在声明时不应该加上 val 关键字。代码如下:
open class Person(val name: String, val age: Int) {// ...}class Student(val studyNo: Int, // 加上 val/var 声明,表面这是一个类变量val score: Int,name: String, // 没有 val/var 声明,表明这只是个构造函数入参,是个局部变量age: Int): Person(name, age) {// ...}
次构造函数
其实大部分场景下是用不到次构造函数的,因为Kotlin提供了默认参数的功能,基本上可以代替次构造函数的作用。
在Kotlin中,一个类只能有一个主构造函数,但是可以有多个次构造函数。当一个类既有主构造函数也有次构造函数的时候,所有的次构造函数都必须调用主构造函数(包括间接调用)。
次构造函数是通过 constructor 关键字来定义的,并且具有函数体。例如下面的例子:
class Student(val studyNo: Int,val score: Int,name: String,age: Int): Person(name, age) {constructor(name: String, age: Int): this("", 0, name, age) {// ...}constructor(): this("", 0) {// ...}}
这里我们定义了两个次构造函数:第一个接收 name 和 age 参数,然后它又通过 this 关键字调用了主构造函数,并将 studyNo 和 score 这两个参数赋值成初始值;第二个次构造函数是无参构造函数,他通过 this 关键字调用我们刚刚定义的第一个次构造函数,将 name 和 age 也设置成初始值,这相当于间接调用了主构造函数。
特殊情况
一个类可以只有次构造函数,没有主构造函数吗?在Kotlin中是可以的,例如下面的例子:
class Student: Person {constructor(name: String, age: Int): super(name, age) {// ...}}
可以看到,由于 Student 类没有主构造函数,所以在继承时,没有直接调用 Person 类的无参构造函数,所以 Person 后面没有括号。然后在次要构造函数的声明时,因为自己没有主构造函数,所以只能调用父类的构造函数,这里需要使用 super 关键字来调用父类的构造函数。
接口
Kotlin与Java一致,都采用了单继承多实现的方式。而Java中继承使用关键字 extends ,实现使用关键字 implement ,Kotlin中则统一使用 : 关键字来声明, 多个接口之间使用逗号来分隔。另外,Kotlin 实现接口时后面不需要加上括号,因为接口没有构造函数可以调用。例如:
interface Study {fun readBooks()fun doHomework()}class Student(name: String, age: Int): Person(name, age), Study {override fun readBooks() {// ...}override fun doHomework() {// ...}}
Kotlin 的接口中的方法允许具有默认实现,但是不用像 Java 一样加上 default 关键字,而是像定义一个普通的类方法一样去定义即可。
