Kotlin初学须知

写在前面

根据 郭霖的《第一行代码》总结,主要用于 Java 转 Kotlin

(幸运地拿到了第一批的签名版~

麻了,有 Kotlin 中文站,此文仅做个人理解

变量与函数

val(value)不可变、var(variable)可变,Kotlin 能类型推导,尽可能用 val

1
2
3
fun methodName(p1: Int, p2: Int): Int {
return 0
}

函数语法糖:

1
2
3
fun methodName(p1: Int, p2: Int): Int = max(p1, p2)
// 类型推导
fun methodName(p1: Int, p2: Int) = max(p1, p2)

逻辑控制

when 类似 switch,还支持 类型匹配is Int -> println("...")

1
2
3
4
5
6
7
8
fun getScore(name: String) = when {
name.startsWith("Tom") -> 86
name == "Jim" -> 77
else -> 0
// else 在后面能改
}

// 字符串和对象比较一律用 ==

val range = 0..10[0,10]

val range = 0 until 10[0,10)

for-in 循环:

1
2
3
4
5
6
7
8
9
for(i in 0..10) {
println(i)
}
for(i in 0 until 10 step 2) { // 每次 i+2,而不是 i++
println(i)
}
for(i in 10 downTo 1) { // [10,1] 降序
println(i)
}

类与对象

默认都是 final,需要此类能被继承需要在类名前面添加 open 关键字:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
open class Person { ... }

interface Study {
fun readBooks()
fun doHomework() {
println("这里是接口方法默认实现")
}
}

// 继承
class Student : Person() { ... }

// 实现接口,直接逗号后面加,也是 :
class Student : Person(), Study { ... }

// 主构造函数:直接类名后面加
class Student(val sno: String, val grade: Int) : Person() { ... }
// 主构可以在 init体 里面加逻辑
class Student(val sno: String, val grade: Int) : Person() {
init {
// ...
}
}

只能有一个主构造函数,但可以有多个次构造函数,但次构造函数 constructor 必须 间接调用 主构造函数 this

1
2
3
4
class Student(val sno: String, val grade: Int, name: String, age: Int) : Person(name, age) {
constructor(name: String, age: Int) : this("", 0, name, age){}
constructor() : this("", 0){}
}

密封类

sealed

when 时可以用到,不用写 else

数据类与单例类

data 关键字直接帮你把 equals()hashCode()toString() 等方法都自动生成:

1
2
data class YourClass(val p1: String, val p2: Int)
// 可省略{}

object 替换 class

1
2
3
4
5
6
7
8
object MySingleton {
fun singletonFun() {
println("hhh")
}
}

// 调用直接
MySingleton.singletonFun()

泛型

语法和 Java 一样

Kotlin 特性:

1
2
3
4
5
6
7
8
9
10
11
// 类型推导
val result = myClass.method<Int>(123)
val result = myClass.method(123)
// 类型限制
class MyClass {
fun <T : Number> method(param: T): T {
return param
}
}
// 默认情况下,所有的泛型都是可以指定成可空类型的
// ->>> 泛型上界默认是 Any?,如果想不为空,Any

outin:声明处型变

类型擦除

函数可见性修饰符

修饰符 Java(4种) Kotlin(4种)
public 所有类可见 所有类可见(默认
private 当前类可见 当前类可见
protected 当前类、子类、同一包路径下的类可见 当前类、子类可见
default 同一包路径下的类可见(默认
internal 同一模块中的类可见

Kotlin奇奇怪怪的函数

listOf 初始化集合(不可变):

1
val list = listOf("A", "B")

mutableListOf 初始化集合(可变):

1
2
val list = mutableListOf("A", "B")
list.add("c")

SetList 一样 → setOfmutableSetOf

对于 MapKotlin 不推荐用 putget 来操作,而是用下标:

1
2
3
4
5
6
7
8
9
10
// to 不是关键字,而是 infix 函数
val map = mapOf("A" to 1, "B" to 2, "C" to 3)
// 存
map["A"] = 2
// 取
val num = map["B"]
// 遍历
for ((fruit, number) in map) {
println("fruit is " + fruit + ", number is " + number)
}

高阶函数

参数或返回值类型是函数的函数就是高阶函数

例如:

1
2
3
fun example(func: (String, Int) -> Unit) { // Unit是妹有返回值
func("hello", 123)
}

对高阶函数的调用:

1
2
3
4
5
6
7
8
9
10
11
fun main() {
test1("abc", 123, ::test12)
}

fun test1(p1: String, p2: Int, func: (String, Int) -> Unit) {
func(p1, p2)
}

fun test12(p1: String, p2: Int) {
println("p1: $p1; p2: $p2")
}

高阶的背后原理还是匿名类调用,即调用 Function 接口产生的匿名类实例的 invoke 方法。

内联函数

内联函数 能够解决高阶函数产生的开销,能直接替换代码

1
2
3
inline fun test1(p1: String, p2: Int, func: (String, Int) -> Unit) {
func(p1, p2)
}

noinline 关键字这是让此参数不要内联:

1
2
3
inline fun test1(noinline func1: (String, Int) -> Unit, func2: (String, Int) -> Unit) {

}

内联函数的缺点是:无法将函数作为参数去传递,只允许传递给另一个内联函数

tips:内联函数能直接在 Lambdareturn,且返回的是外层的函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
// 内联
fun main(args: Array<String>) {
println("main start")
test1("abc") {
println("Lambda start")
// 直接返回
return
println(it)
println("Lambda end")
}
println("main end")
}

inline fun test1(str: String, func1: (String) -> Unit){
func1(str)
}
// 输出:
// main start
// Lambda start

// 非内联
fun main(args: Array<String>) {
println("main start")
test1("abc") {
println("Lambda start")
// 只能局部返回
return@test1
println(it)
println("Lambda end")
}
println("main end")
}

fun test1(str: String, func1: (String) -> Unit){
func1(str)
}
// 输出:
// main start
// Lambda start
// main end

郭霖:“将高阶函数声明成内联函数是一种良好的编程习惯”

tips:在内联函数中创建另外的 Lambda 或匿名类实现,并在其中调用函数参数,会报错

原因:在另外的 Lambda 或匿名类实现中无法 return

解决:用 crossinline 修饰函数参数,保证不会用 return

标准函数

with、run、apply

定义静态方法

1
2
3
class Util { fun doAction1() { println("do action1") }
companion object { fun doAction2() {
println("do action2") } } }

扩展函数

类名.扩展函数

运算符重载

协程

GlobalScope.launch 函数、delay() 函数、runBlocking 函数、launch 函数(子协程)、suspend 关键字、coroutineScope 函数(coroutine:协程)、

1
2
3
4
val job = Job()
val scope = CoroutineScope(job) scope.launch { // 处理具体的逻辑
}
job.cancel()

async 函数和 await

withContext 函数

suspendCoroutine 函数

待更新…

  • companion object@JvmStatic、顶层方法、aslateinitsealed class、扩展函数、运算符重载、为什么取消 checked exception、泛型上界 Any?和Any、类委托(by)和委托属性(by)、懒加载 by lazy {...} 就是委托属性、A to BA.to(B)infix、泛型实化(reified)、
  • 编译时判空、人为为空、判空辅助
    • ?.?:!!
  • 标准函数
    • letwithrunapplyrepeat