本章节建议重点学习!!!基础不牢,地动山摇!!
语法
这里列举的都是Kotlin和其他语言相比所独有的特征,Kotlin基本语法需要习惯化的点:
- 只使用val(不可变) 和 var(可变) 来定义变量,强制指定使用冒号加类型;
- 基本数据类型类似Python是对象,更加强调面向对象编程;
- Kotlin完全舍弃了new关键字,因此创建匿名类实例的时候就不能再使用new了,而是改用了object关键字。
函数
以下是函数的一些语法糖:
当一个函数中只有一行代码时,Kotlin允许我们不必编写函数体,可以直接将唯一的一行代码写在函数定义的尾部,中间用等号连接即可(注意Kotlin的自动推导很厉害的,一行代码如果有返回值,可以不用显示强调返回类型,会自动推导出来):
1
2
3// 下面两句话等价,并且编辑器提示会正确出现返回值类型
fun largerNumber(num1: Int, num2: Int): Int = max(num1, num2)
fun largerNumber(num1: Int, num2: Int) = max(num1, num2)
条件
if的语法糖
Kotlin中的if语句有一个额外的功能,它是可以有返回值的,返回值就是if语句每一个条件中最后一行代码的返回值;
1
2
3
4
5fun largerNumber(num1: Int, num2: Int) = if (num1 > num2) {
num1
} else {
num2
}
when语法和语法糖
when语句允许传入一个任意类型的参数,然后可以在when的结构体中定义一系列的条件,格式 是:
1
匹配值 -> { 执行逻辑 }
基本匹配和类型匹配:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16// 基本匹配
fun getScore(name: String) = when (name) {
"Tom" -> 86
"Jim" -> 77
"Jack" -> 95
"Lily" -> 100
else -> 0
}
// 类型匹配 is 是核心
fun checkNumber(num: Number) {
when (num) {
is Int -> println("number is Int")
is Double -> println("number is Double")
else -> println("number not support")
}
}不带参数写法,拓展性强:
1
2
3
4
5
6
7fun getScore(name: String) = when {
name == "Tom" -> 86
name == "Jim" -> 77
name == "Jack" -> 95
name == "Lily" -> 100
else -> 0
}
循环
区间
区间[0, 10]
1
val range = 0..10
区间[0, 10)
1
val range = 0 until 10
步进
1
val range = 0 .. 10 step 2
倒序
1
val range = 10 downTo 0
for-in
已丢弃掉for-i循环,转投for-in。
对象
继承与构造
在Kotlin中任何一个非抽象类默认都是不可以被继承的,加上open关键字之后,类就允许被继承;
主构造函数的特点是没有函数体,Kotlin给我们提供了一个init结构体,所有主构造函数中的逻辑都可以写在里面:
1
2
3
4
5
6
7
8
9open class Person {
...
}
class Student(val sno: String, val grade: Int) : Person() {
init {
println("sno is " + sno)
println("grade is " + grade)
}
}调用父类构造函数,传递参数不需要变量声明,否则会错误。
1
2
3
4
5
6
7open class Person(val name: String, val age: Int) {
...
}
class Student(val sno: String, val grade: Int, name: String, age: Int) :
Person(name, age) {
...
}我们在Student类的主构造函数中增加name和age这两个字段时,不能再将它们声明成val,因为在主构造函数中声明成val或者var的参数将自动成为该类的字段,这就会导致和父类中同名的name和age字段造成冲突。因此,这里的name和age参数前面我们不用加任何关键字,让它的作用域仅限定在主构造函数当中即可
Kotlin规定,当一个类既有主构造函数又有次构造函数时,所有的次构造函数都必须调用主构造函数(包括间接调用)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17open class Person(val name: String, val age: Int) {
...
}
class Student(val sno: String, val grade: Int, name: String, age: Int) :
Person(name, age) {
// 次构造函数1
constructor(name: String, age: Int) : this("", 0, name, age) {
}
// 次构造函数2
constructor() : this("", 0) {
}
}
val student1 = Student() // 本质是先调用次构造函数2,由次构造函数2调用次构造函数1,次构造函数1再调用主构造函数
val student2 = Student("Jack", 19) // 本质是先调用次构造函数1,次构造函数1再调用主构造函数
val student3 = Student("a123", 5, "Jack", 19) // 直接调用主构造函数只有次构造函数,没有主构造函数
1
2
3
4
5
6
7open class Person(val name: String, val age: Int) {
...
}
class Student : Person {
constructor(name: String, age: Int) : super(name, age) {
}
}总之,必须调用到root类的主构造函数,这是本质。注意这里的代码变化,首先Student类的后面没有显式地定义主构造函数,同时又因为定义了次构造函数,所以现在Student类是没有主构造函数的。那么既然没有主构造函数,继承 Person类的时候也就不需要再加上括号了。
接口
- 接口函数使用interface定义;
- 实现接口和继承类似,但是接口没有构造函数,因此不用加括号;
- Kotlin 使用override关键字来重写父类或者实现接口中的函数;
- 函数接受接口类作为参数,在函数内部调用接口方法,叫面向接口编程,简称多态。
可见性修饰符
- private,当前类内部可见;
- public,所有类可见,是默认项;
- protected,当前类和子项可见;
- internal,同一模块中可见。
数据类和单例类
- 数据类通常需要重写equals(),hashCode(),toString() 这几个方法;
- Kotlin 只需要声明data关键字,表明这个类是一个数据类,其中固定和无实际逻辑的方法自动生成;
- Kotlin 中,单例类生成使用object关键字取代class关键字即可,不需要私有化构造函数;
- 调用单例类,不需要实例化,可以类似静态方法的调用方式执行,但是其实背后是Kotlin 自动创建了一个实例。
Lambda编程
Lambda表达式的语法结构:
1 | {参数名1 : 参数类型, 参数名2 : 参数类型 -> 函数体} |
ArrayList和HashSet
ArrayList简称List,HashSet简称Set。
- Kotlin 专门提供内置函数listOf() 来简化初始化集合,创建的是一个不可变的集合;
- mutableListOf() 函数创建的是一个可变的集合;
- Set集合不可以存放重复元素,这是异于List 集合的最大不同。
- Kotlin 同样提供了setOf() 和mutableSetOf() ,以简化set用法
HashMap
HashMap简称map。
Kotlin 中不建议使用put() 和 get() 方法对map进行添加和读取数据操作,而是更加推荐使用类似于数组下标的语法结构。
1
2map["Apple"] = 1 // 写入
val number = map["Apple"] // 读取Kotlin 同样提供了mapOf() 和mutableMapOf() ,以简化map用法。
1
val map = mapOf("Apple" to 1, "Banana" to 2, "Orange" to 3, "Pear" to 4, "Grape" to 5)
键值对使用to关联,而to不是关键字,这是一种infix函数,在后续学习。
集合函数式API
maxBy就是一个普通的函数,它接收的是一个Lambda类型的参数,并且会在遍历集合时将每次遍历的值作为参数传递给Lambda表达式。maxBy函数的工作原理是根据 我们传入的条件来遍历集合,从而找到该条件下的最大值;
Kotlin 规定,当Lambda参数是函数最后一个参数时,可以将Lambda表达式移到函数括号外面;
1
val maxLengthFruit = list.maxBy() { fruit: String -> fruit.length }
如果Lambda参数是函数唯一一个参数的话,还可以将函数括号省略;
1
val maxLengthFruit = list.maxBy { fruit -> fruit.length }
当Lambda表达式的参数列表中只有一个参数时,不必声明参数名,可以使用it 关键字替代。
1
val maxLengthFruit = list.maxBy { it.length }
map 它用于将集合中的每个元素都映射成一个另外的值,映射的规则在Lambda表达式中指定,最终生成一个新的集合;
1
val newList = list.map { it.toUpperCase() } // 将名字全部转化为大写
filter是用来过滤集合中的数据的;
1
val newList = list.filter { it.length <= 5 } // 获取长度小于5的名字
any用于判断集合中是否至少存在一个元素满足指定条件;
all用于判断集合中是否所有元素都满足指定条件。
Java函数式API
针对Runnable接口进行讲解。
因为Runnable类中只有一个待实现方法,即使这里没有显式地重写run()方法,Kotlin也能自动明白Runnable后面的Lambda表达式就是要在run()方法中实现的内容,否则使用override重写多个方法函数。
1
2
3Thread(Runnable {
println("Thread is running")
}).start()一个Java方法的参数列表中有且仅有一个Java单抽象方法接口参数,我们还可以将接口名进行省略;
1
2
3Thread({
println("Thread is running")
}).start()当Lambda表达式是方法的最后一个参数时,可以将Lambda表达式移到方法括号的外面。同时,如果Lambda表达式还是方法的唯一一个参数,还可以将方法的括号省略。
1
2
3Thread {
println("Thread is running")
}.start()
空指针检查
Kotlin默认所有的参数和变量都不可为空。
判空辅助工具
最常用的是 ?. 操作符,这个操作符的作用非常好理解,就是当对象不为空时正常调用相应的方法,当对象为空时则什么都不做;
1
a?.doSomething()
另外一个非常常用的 ?: 操作符,这个操作符的左右两边都接收一个表达式,如果左边表达式的结果不为空就返回左边表达式的结果,否则就返回右边表达式的结果;
1
val c = a ?: b
如果我们想要强行通过编译,可以使用非空断言工具,写法是在对象的后面加上 !! ,如下所示:
1
2
3
4fun printUpperCase() {
val upperCase = content!!.toUpperCase()
println(upperCase)
}一个比较与众不同的辅助工具——let。let既不是操作符,也不是什么关键字,而是一个函数。这个函数提供了函数式API的编程接口,并将原始调用对象作为参数传递到 Lambda表达式中。
1
2
3
4
5
6
7
8
9
10
11obj.let { obj2 ->
// 编写具体的业务逻辑
}
var study: Study? = null
fun doStudy(study: Study?) {
study?.let {
it.readBooks()
it.doHomework()
}
}这里调用了obj对象的let函数,然后Lambda表达式中的代码就会立即执行,并且这个obj对象本身还会作为参数传递到Lambda表达式中。不过,为了防止变量重名,这里我将参数名改成了obj2,但实际上它们是同一个对象,这就是let函数的作用。
全局变量的值随时都有可能被其他线程所修改,即使做了判空处理,仍然无法保证if语句中的study变量没有空指针风险,仍会报错:
1
2
3
4
5
6
7var study: Study? = null
fun doStudy() {
if (study != null) {
study.readBooks()
study.doHomework()
}
}
Kotlin 中的小魔术
字符串内嵌表达式,Kotlin允许我们在字符串里嵌入 ${} 这种语法结构的表达式,并在运行时使用表达式执行的结果替代这一部分内容:
1
"hello, ${obj.name}. nice to meet you!"
函数的参数默认值,可以通过键值对的方式来传参。
次构造函数在Kotlin中很少用,因为Kotlin 提供了给函数设定参数默认值的功能,它在很大程度上能够替代次构造函数的作用。