Kotlin 学习笔记

ToC

前言

终于有一天,被咖啡 ️ 折磨疯的泡泡开始学起了 Kt

只是 记录一些简单的知识点

正文

关键字

就这关键字数量看得我头皮发麻

记录几个怪的

范型虽然可以代表所有有所有权的类型,但失去了一个具体类型具有的某些功能

例如类型判断(只针对 java 这种靠类型擦除技术实现的泛型),如果没有 reified 修饰 T 和 inline 修饰函数 p !is T 会爆红,因为编译把 T 抹了会有语法错误

下面的代码会被在调用处被展平,T 会被替换成调用时传入的类型

inline fun <reified T TreeNode.findParentOfType(): T? {
    var p = parent
    while (p != null && p !is T) {
        p = p.parent
    }
    return p as T?
}

和多平台有关,由 expect 修饰一个函数签名,来定义一个不同平台上的函数 接着由 actual 修饰对应平台实现的代码

//Common
expect fun randomUUID(): String

//Android
import java.util.*
actual fun randomUUID() = UUID.randomUUID().toString()

//iOS
import platform.Foundation.NSUUID
actual fun randomUUID(): String = NSUUID().UUIDString()

//...

修饰一个尾递归的函数,编译时优化成迭代

操作符重载,还可以用于属性委托 修饰一个成员方法或者一个拓展函数

字如其名,给类型起别称的

typealias NodeSet = Set<Network.Node>

typealias MyHandler = (Int, String, Any) - Unit

typealias Predicate<T = (T) - Boolean

当 kotlin 的部分奇妙语法的注解转换到 java 上需要对注解进行合理注释

xxx 可以为

函数最后一个参数是 lambda 表达式就可以放到括号外面

fun foo(input: String, call: (String) - Boolean) {
    println(call(input))
}

fun main() {
    foo("1") {
        str ->  when(str) { // 可以用 when(it) {
            "1" -> true
            else -> false
        }
    }
}

// 如果只有一个参数并且是lambda表达式的话,圆括号都能省去

var data = ...
data.run {
    println(data)
}

if, when(类似switch)等不仅仅是语句块,而是表达式,可以返回一个值给一个变量赋值

for循环不能经典三段式了,取而代之的是for in结构

// 简单
// 1..100 表示从 1 到 100 的范围,左闭右闭
for (i in 1..100) {
    println("Hi")
}

// 倒序 100 到 1
// downTo 是 Int 的拓展中缀函数,类似于运算符(运算等级最低) 等价 100.downTo(1)
for (i in 100 downTo 1) {
    println("Hello${i}")
}

为空执行&不为空执行 ( That's pretty good 💗

var data = ...
// data 不为空执行,空检测
data?.run {
    println(data)
}

// data 为空执行
data?:run {
	println("NULL")
}

常用 ADT

除了 Set,其余的都可以用 [] 访问元素(推荐)

let, with, run, apply, also 使用

摘自 https://blog.csdn.net/u013064109/article/details/78786646 (编程全靠 CSDN)

函数inline 结构函数体内使用的对象返回值是否是扩展函数
letfun <T, R> T.let(block: (T) -> R): R = block(this)it闭包形式返回
withfun <T, R> with(receiver: T, block: T.() -> R): R = receiver.block()this闭包形式返回
runfun <T, R> T.run(block: T.() -> R): R = block()this闭包形式返回
applyfun T.apply(block: T.() -> Unit): T { block(); return this }this返回 this
alsofun T.also(block: (T) -> Unit): T { block(this); return this }it返回 this

Kotlin 中的类

sealed class Animal
data class Dog(val age: Int) : Animal()
data class Cat(val age: Int) : Animal()
object NotAnimal : Animal()

// 使用 sealed class 后, when判断类型不用写else
fun getAge(animal: Animal): Int = when(animal) {
    is Dog -> animal.age
    is Cat -> animal.age
    NotAnimal -> Int.NaN
}

Kotlin 中的构造函数

分为:

主构造函数声明在类名称的后面

fun main() {
    val s = Student()
    println(s.name)
}

@Suppress("UNUSED")
class Student(sno: String, grade: Int) : Person("田所浩二") {

    init {
        if (sno == "114514" || grade == 1919810) {
            println("啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊")
        }
    }

    constructor() : this("114514", 1919810)
}

open class Person(var name: String)

次构造函数由 constructor() 命名,必须直接或间接调用主构造函数

@Suppress("UNUSED")
class Student(sno: String, grade: Int) : Person("田所浩二") {

    init {
        if (sno == "114514" || grade == 1919810) {
            println("啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊")
        }
    }

    constructor(sno: String) : this(sno, 1919810)

    constructor() : this("114514")

}

无主构造函数的特殊情况

@Suppress("UNUSED")
class Student : Person {
    private val sno : String

    constructor(name: String, sno: String) : super(name) {
        this.sno = sno
    }

    constructor(name: String) : super(name) {
        this.sno = "null"
    }
}

open class Person(var name: String)

可见性修饰符

懒加载

和 OC 差不多,不过实现起来比 OC 简单

val lazyValue: String by lazy {
    println("computed!")
    "Hello"
}

fun main(args: Array<String>) {
    println(lazyValue)
    println(lazyValue)
}

// 打印结果
// computed!
// Hello

// Hello

协程

待填坑

inline, noinline, crossinline 介绍

带有 inline(内联)的函数编译后会被展开放到调用处,连同函数的 lambda 参数也会被展开

fun main() {
    testFunc1("hi")
}

// 这个 inline 没有意义,甚至会反优化到处展开导致字节码膨胀
inline fun testFunc1(str: String) {
    println(str)
}

// 编译后大概会变成这样
fun main() {
    println("hi")
}
fun main() {
    testFunc1("hello") {
        println(" world!")
    }
}

inline fun testFunc1(str: String, lam : () -> Unit) {
    print(str)
    lam()
}

// 大概编译后会变成这样
// 这样kotlin就不会创建函数对象包裹lambda表达式了,这个函数就可以放到高频率的地方使用了
fun main() {
    print("hello")
    println(" world!")
}

noinline 就是阻止内联,放到函数参数,这个函数参数就不会被展开

防止返回这个lambda对象时被展开了它就没了,那我返回个嘚


fun main() {
    val repeat = testFunc1("paopao", {
        print("hello! ")
    }, {
        println(" bye!")
    })
    repeat()
}

inline fun testFunc1(str: String, lam1 : () -> Unit, noinline lam2 : () -> Unit): () -> Unit {
    lam1()
    print(str)
    lam2()
    return lam2
}

// 大概这样
fun main() {
    print("hello! ")
    print("paopao")
    val repeat = {
        println(" bye!")
    }
    repeat()
}

以上解释了为什么 kt 里的不是内联函数的 lambda 参数不允许使用 return ( return@xxx 的除外)

带有 inline 的高阶函数会把函数参数(lambda)展开,放到调用处,那这个 return 就直接把调用的函数结束了...

不带 inline 的高阶函数则不会,那我写一个 lambda 还要考虑 return 到底结束哪个函数,kt 索性就: 你不是内联函数的 lambda 参数,return 就别用了,使用最后一行表达式的值作为 lambda 返回值得了

所以,内联函数的 lambda 参数里的 return 结束的是外面的函数

同样又有一个问题,如果内联函数这个 lambda 的执行和这个内联函数差了几个栈帧怎么办?

// 例如这样
inline fun testFunc1(lam1 : () -> Unit, noinline lam2 : () -> Unit): () -> Unit {
    {
        lam1() // 这个lambda会在另一个栈帧内执行
    }()
    lam2()
    return lam2
}

fun main() {
	testFunc1({
        println("hi")
        return
    }, {
        println("I am ok")
    })
}

lam1 里的 return 到底结束了谁???? 不是说好了结束 main 的吗?

其实这样会报错,编译不了,内联函数里 lambda 参数不允许间接调用

想用? 加 crossinline

inline fun testFunc1(crossinline lam1 : () -> Unit, noinline lam2 : () -> Unit): () -> Unit {
    {
        lam1() // 这个lambda会在另一个栈帧内执行
    }()
    lam2()
    return lam2
}

fun main() {
	testFunc1({
        println("hi")
        //return
    }, {
        println("I am ok")
    })
}

与此同时,lam1 内将不能使用 return

infix 中缀表示法

说实在的,我不懂它存在的意义... 🍬 多了会吃腻

// 然后就可以像用 + - * / 一样用这个方法

infix fun Int.add(x: Int): Int {
	return 1 + 2
}

// 用中缀表示法调用该函数
1 add 2

// 等同于这样
1.add(2)

属性委托

协变和逆变

待填坑