Kotlin知识点

394 阅读13分钟

学习站点

  1. Kotlin官网
  2. Google 学习 Kotlin 编程语言
  3. GitHub from-java-to-kotlin
  4. 菜鸟教程 Kotlin

文章

  1. Kotlin下的5种单例模式
  2. 一文彻底搞懂Kotlin中的委托
  3. 如何更好地使用 Kotlin 语法糖封装工具类

知识点

类的构造函数及init代码块调用顺序

  • init代码块 -> 构造函数
  • 示例
class L3C1 {
    var attr1:String? = null

    constructor(a1:String){
        Log.i("L3C1","constructor. start.")
        attr1 = a1
        Log.i("L3C1","constructor. end.")
    }
    init {
        Log.i("L3C1","init")
    }
}

fun showConstructorInit(view: View) {
    val bean:L3C1 = L3C1("testA1")
}

日志打印:
2023-10-31 13:55:00.763 4588-4588/com.example.jet2022 I/L3C1: init
2023-10-31 13:55:00.763 4588-4588/com.example.jet2022 I/L3C1: constructor. start.
2023-10-31 13:55:00.763 4588-4588/com.example.jet2022 I/L3C1: constructor. end.

val使用get可以返回动态值

class L3C2 {
    val a1: String
        get() {
            val result = "" + Math.random()
            Log.i("L3C2", "a1. get. $result")
            return result
        }
}

fun showValDynamicValue(view: View) {
    val bean:L3C2 = L3C2()
    var a = bean.a1
    a = bean.a1
    a = bean.a1
}

2023-10-31 16:36:01.610 21094-21094/com.example.jet2022 I/L3C2: a1. get. 0.10962064620199585
2023-10-31 16:36:01.610 21094-21094/com.example.jet2022 I/L3C2: a1. get. 0.06812714631783268
2023-10-31 16:36:01.610 21094-21094/com.example.jet2022 I/L3C2: a1. get. 0.33978948467382875

object实现单例

object L3O1 {
    val a1: String
        get() {
            Log.i("L3O1","a1. get.")
            return Math.random().toString() + "1111111"
        }
    fun f1(){
        Log.i("L3O1","f1")
    }
}

fun showObjectSingleInstance(view: View) {
    L3O1.a1
    L3O1.f1()
}

2023-10-31 16:53:24.389 23115-23115/com.example.jet2022 I/L3O1: a1. get.
2023-10-31 16:53:24.390 23115-23115/com.example.jet2022 I/L3O1: f1

主构造函数

  • 和class一起声明的constructor是主构造函数
    • 主构造函数中的值可以给类中的属性引用,也可以在init中引用
    • 主构造函数中使用var或val声明属性
      • 该类中包含同名可变属性及不可变属性
      • 该类中上述属性值就是主构造函数传入值
  • class内部的constructor属于次级构造参数
    • 次级构造函数声明时,必须继承主构造函数
  • 示例
//和类一起声明的构造函数是主构造函数
//主构造函数中的值可以给类中的属性引用,也可以在init中引用
class L4C1 constructor(a1: String, a2: String) {
    var a1: String = a1
    var a2: String = a2
    var a3: String = ""

    init {
        println(a1)
        println(a2)
    }

    //次级构造函数声明时,必须继承主构造函数
    constructor(a3: String) : this("a1", "a2") {
        this.a3 = a3
    }
}

//主构造函数中使用var或val声明属性
//1:该类中包含同名可变属性及不可变属性
//2:该类中上述属性值就是主构造函数传入值
class L4C2 constructor(var a1: String, val a2: String) {
}

Kotlin泛型

out 和 in
  1. out
    • 默认写法,无法将一个子类型List赋值给1个声明的父类型List
    • 声明中使用out,可以解决该问题
    • 使用out后,get支持,add,set不支持
    var list1:MutableList<out TextView> = arrayListOf<Button>(
        Button(this,null,0),
        Button(this,null,0),
        Button(this,null,0)
    )
    
  2. in
    • 默认写法,无法将一个父类型List赋值给1个声明的子类型List
    • 声明中使用in,可以解决该问题
    • 使用in后,get不支持,add,set支持
      • get后强转是可以的
    var list2:MutableList<in TextView> = arrayListOf<View>(
        Button(this,null,0),
        Button(this,null,0),
        Button(this,null,0)
    )
    
可以在类声明的时候,给泛型符T增加out和in关键字
//可以在声明类时,给泛型符T添加out关键字
class COUT<out T> {
}

//可以在声明类时,给泛型符T添加in关键字
class CIN<in T> {
    fun f1(t:T){

    }
}

fun f2(){
    //可以在类声明的时候,给泛型符T加out或in关键字
    val cout:COUT<View> = COUT<ViewGroup>()
    val cin:CIN<ViewGroup> = CIN<View>()
    cin.f1(LinearLayout(this,null,0))
}
where支持为泛型类设置多个上界
interface PI1 {
    fun pi1func1()
}

interface PI2 {
    fun pi2func1()
}

class CPI1PI2 : PI1,PI2 {
    override fun pi1func1() {
    }
    override fun pi2func1() {
    }
}

//where可以为类泛型符T设置多个上界
class CC1<T> where T : PI1, T : PI2 {
    var instance:T? = null
    fun addInstance(t:T){
        instance = t
    }

    fun exeFunc(){
        instance?.pi1func1()
        instance?.pi2func1()
    }
}

fun f4(){
    val item:CC1<CPI1PI2> = CC1<CPI1PI2>()
    item.addInstance(CPI1PI2())
    item.exeFunc()
}
inline和reified结合,可以在运行时判断泛型实例的具体类型
/*fun <T> f3(p1:Any){
    if (p1 is T){
        println("111")
    }
}*/
//inline和reified结合,可以在运行时判断泛型实例确切类型
inline fun <reified T> f3(p1:Any){
    if (p1 is T){
        println("111")
    }
}
当你没有对泛型参数应用任何边界时,它默认为 Any?
'lateinit' modifier is not allowed on properties of a type with nullable upper bound

class C2<T1, T2> {
    //因为泛型未添加限制,其类型是Any?,对于可空类型变量,无法使用lateinit
    lateinit var t1: T1
    lateinit var t2: T2
}

要这样改:如果不限制泛型类型范围,让其继承ny

class C2<T1 : Any, T2 : Any> {
    lateinit var t1: T1
    lateinit var t2: T2

    fun setInstance(t1: T1, t2: T2) {
        this.t1 = t1
        this.t2 = t2
    }

    fun print() {
        Log.i("TagC2", "print. t1:$t1 ; t2:$t2")
    }
}

fun testTClass(view: View) {
    val item: C2<String, Int> = C2()
    item.setInstance("111", 222)
    item.print()
}

Kotlin协程简单使用

  1. Android项目引入协程
E:\Data\Code\Jet\Jet2022\app\build.gradle

dependencies {

    implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.3.1"
    implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.3.1'

    ***
}
  1. 代码中构建协程,在协程中执行多线程协作
fun doFunc1(view: View) {
    GlobalScope.launch(Dispatchers.Main) {
        //闭包里面的代码块称为1个协程

        var a1: Int = withContext(Dispatchers.IO) {
            Log.i(TAG, "gin a1")
            100
        }
        var a2: Int = withContext(Dispatchers.Default) {
            Log.i(TAG, "gain a2")
            200
        }
        Log.i(TAG, "result:${a1 + a2}")
    }
    val cs: CoroutineScope = CoroutineScope(Dispatchers.IO)
    cs.launch {
        //闭包里面的代码块称为1个协程

        Log.i(TAG, "111")
    }
    Log.i(TAG, "isActive:${cs.isActive}")
}

2023-11-07 17:46:10.956 2303-2303/com.example.jet2022 I/TagKotlinLesson6Activity: isActive:true
2023-11-07 17:46:10.958 2303-2407/com.example.jet2022 I/TagKotlinLesson6Activity: 111
2023-11-07 17:46:10.963 2303-2407/com.example.jet2022 I/TagKotlinLesson6Activity: gin a1
2023-11-07 17:46:10.968 2303-2425/com.example.jet2022 I/TagKotlinLesson6Activity: gain a2
2023-11-07 17:46:10.968 2303-2303/com.example.jet2022 I/TagKotlinLesson6Activity: result:300

协程中调度器/Dispatchers有三种,适用场景

  1. Dispatchers.Main
    • Android主线程
  2. Dispatchers.IO
    • 读写文件,操作数据库,网络请求
  3. Dispatchers.Default
    • CPU密集型任务,比如大量的计算

delay可以实现等待一段时间的效果

suspend fun f1(): String {
    Log.i("KotlinLesson7Activity", "1111111")
    delay(2000)
    Log.i("KotlinLesson7Activity", "2222222")
    return withContext(Dispatchers.Default) {
        Log.i("KotlinLesson7Activity", "3333333")
        "333333333"
    }
}

fun testSuspend(view: View) {
    Log.i("KotlinLesson7Activity", "testSuspend. start.")
    GlobalScope.launch(Dispatchers.IO) {
        f1()
    }
    Log.i("KotlinLesson7Activity", "testSuspend. end.")
}

2023-11-14 14:57:48.586 9639-9639/com.example.jet2022 I/KotlinLesson7Activity: testSuspend. start.
2023-11-14 14:57:48.635 9639-9639/com.example.jet2022 I/KotlinLesson7Activity: testSuspend. end.
2023-11-14 14:57:48.638 9639-9706/com.example.jet2022 I/KotlinLesson7Activity: 1111111
2023-11-14 14:57:50.646 9639-9706/com.example.jet2022 I/KotlinLesson7Activity: 2222222
2023-11-14 14:57:50.651 9639-9708/com.example.jet2022 I/KotlinLesson7Activity: 3333333

扩展函数

扩展函数包括无参数的扩展函数,有参数的扩展函数
class L10C1 {
    //类中的扩展函数,只能在类里面被调用
    fun String.f1(){
        Log.i("L10C1","String.f1")
    }
    fun f2(){
        "".f1()
    }
}

fun f3(){
    "".f2()
}

//类外面的扩展函数,可以被其他类调用
//无参数
fun String.f2(){
}

//有参数的类的扩展函数
fun String.f3(p1:Int, p2:Float){
    Log.i("L10C1","String.f3")
}

扩展函数的调用方法:

fun f1() {
    //无参数的扩展函数调用方式
    "".f2()
    String::f2.invoke("111111111111")
    (String::f2)("111111111")

    //有参数的扩展函数调用方式
    "".f3(1, 1F)
    String::f3.invoke("StringSource", 1, 2F)
    (String::f3)("StringSource", 1, 2F)
}
可以将扩展函数的引用赋值给变量
fun f2() {
    //将扩展函数的引用赋值给变量
    //无参数的扩展函数
    val sf2: String.() -> Unit = String::f2
    sf2.invoke("1111111")
    sf2("1111")
    "11111".sf2()

    //有参数的扩展函数
    val sf3: String.(Int, Float) -> Unit = String::f3
    sf3.invoke("22222", 1, 2F)
    sf3("222222", 1, 2F)
    "222222".sf3(1, 2F)
}
扩展函数的引用有2种写法,且2种写法声明的变量可以互相赋值
fun f3() {
    //扩展函数的引用,有2种写法
    //1:无参数的扩展函数
    val func1: String.() -> Unit = String::f2
    val func2: (String) -> Unit = String::f2
    func1.invoke("1111111")
    func1("1111111")
    "11111111".func1()

    func2.invoke("1111111")
    func2("1111111")
    //这样调用是不行的
    //"11111111".func2()

    //两种类型的写法声明的变量可以互相赋值
    val func3: String.() -> Unit = func2
    val func4: (String) -> Unit = func1

    //2: 有参数的扩展函数
    val func5: String.(Int, Float) -> Unit = String::f3
    val func6: (String, Int, Float) -> Unit = String::f3
    func5.invoke("111111", 1, 2F)
    func5("11111", 1, 2F)
    "111111111".func5(1, 2F)
    func6.invoke("111111", 1, 2F)
    func6("11111", 1, 2F)
    //这样调用是不行的
    //"1111111".func6(1,2F)

    val func7: String.(Int, Float) -> Unit = func6
    val func8: (String, Int, Float) -> Unit = func5
}
因为上述两种写法声明的变量可以互相赋值,进而:
  1. 可以将一个扩展函数的引用赋值给一个非扩展函数类型声明的变量
    • 可以禁止该变量的 Instance.variableName(params...) 调用方式
  2. 可以将一个非扩展函数的引用赋值给一个扩展函数类型声明的变量
    • 可以增加该变量的 Instance.variableName(params...) 调用方式

示例:

fun Int.f5(p1: String, p2: String) {
}
fun f6(p1: Int, p2: String, p3: String) {
}

fun f4() {
    //因为上述两种写法声明的变量可以互相赋值,进而:
    //1: 可以将一个扩展函数的引用赋值给一个非扩展函数类型声明的变量
    //1.1: 可以禁止该变量的 Instance.variableName(params...) 调用方式
    val f1: (Int, String, String) -> Unit = Int::f5
    //这样会报错
    //100.f1("111","222")

    //2: 可以将一个非扩展函数的引用赋值给一个扩展函数类型声明的变量
    //2.1: 可以增加该变量的 Instance.variableName(params...) 调用方式
    val f2: Int.(String, String) -> Unit = ::f6
    //可以这样调用
    100.f2("1111", "2222")
}

扩展属性

class L10c2 {
    var a1: String = ""
        get() {
            return ""
        }
        set(value) {
            field = value
        }
    private var a11:String = "a11"
}
var L10c2.a2:Int
    get() {
        return 10 * this.a1.toInt()
    }
    set(value) {
        //注意:扩展属性无法真正为类增加一个属性,如果一定要存储扩展属性的值,可以利用类的非扩展属性
        this.a1 = value.toString()
        //扩展属性无法访问类的private声明的属性
        //this.a11
    }
val L10c2.a3:String
    get() {
        return "任意值"
    }

fun f5(){
    var obj:L10c2 = L10c2()
    //a2, a3 都是L10c2类的扩展属性
    obj.a2 = 200
    var a3Value = obj.a3
}
  1. 类的扩展属性不能有初始化值:下面写法就是错的,Initializer is not allowed here because this property has no backing field
    var L10c2.a2:Int = 100
        ***
    
  2. 类的扩展属性无法为类真正增加一个属性,所以set方法可以利用该类的非扩展属性
  3. 类的扩展属性无法访问类的private属性

inline、noinline、crossinline

高阶函数:可以以其他函数作为方法参数或返回值的函数
  1. 高阶函数调用时,函数参数可以用fun,也可以直接写lambda表达式
  2. fun中的return,仅结束当前函数类型参数的执行,不会结束外部函数整体执行
  3. lambda表达式中,不能直接使用return
fun f1(p1: String, func1: () -> Unit, func2: (String, String) -> Unit): Unit {
    Log.i("TagInline","f1. step1")
    func1()
    Log.i("TagInline","f1. step2")
    func2(p1, p1)
}

fun exef1() {
    f1(
        "111",
        fun() {
            //在fun中的return,仅仅结束当前函数类型参数的执行,不会结束外部函数f1整体执行流程
            Log.i("TagInline","exef1. func1")
            return
        },
        fun(s1: String, s2: String) {
            Log.i("TagInline","exef1. func2")
        }
    )

    f1(
        "111",
        {
            Log.i("TagInline","exef1. func1")
            //在lambda表达式中不可以直接使用return
            //return
        },
        { s1, s2 ->
            Log.i("TagInline","exef1. func2")
        }
    )
}

2023-11-16 17:17:23.146 18931-18931 I/TagInline: f1. step1
2023-11-16 17:17:23.146 18931-18931 I/TagInline: exef1. func1
2023-11-16 17:17:23.146 18931-18931 I/TagInline: f1. step2
2023-11-16 17:17:23.146 18931-18931 I/TagInline: exef1. func2

2023-11-16 17:17:23.146 18931-18931 I/TagInline: f1. step1
2023-11-16 17:17:23.146 18931-18931 I/TagInline: exef1. func1
2023-11-16 17:17:23.146 18931-18931 I/TagInline: f1. step2
2023-11-16 17:17:23.146 18931-18931 I/TagInline: exef1. func2
inline及noinline
  1. 使用inline修饰的函数,在被调用时,会将其函数体,其函数类型的参数铺平,直接拷贝至调用处.
  2. 后果:其函数类型参数本来是一个对象,被铺平后,该对象就消失了,无法再将其函数参数作为对象使用.
    • 如何解开这种限制,使用noinline修饰函数类型参数,可以继续将该参数当做对象使用
  3. 好处:
    • 本来高阶函数中的函数类型参数,会被转换为个对象,如果该高阶函数会循环或频繁调用,就会大量创建对象引发性能问题;使用inline后直接将函数类型参数逻辑铺平拷贝过来,避免了大量创建对象损耗性能.
    • 内联函数的Lambda参数中,可以使用return.
      • 此处return会结束Lambda表达式的外部的外部的执行
private fun exef2Mid1(){
    Log.i("TagInline","exef2Mid1. start.")
    exef2Mid2()
    Log.i("TagInline","exef2Mid1. end.")
}
private fun exef2Mid2(){
    Log.i("TagInline","exef2Mid2. start.")
    exef2Mid3()
    Log.i("TagInline","exef2Mid2. end.")
}
private fun exef2Mid3(){
    Log.i("TagInline","exef2Mid3. start.")
    exef2()
    Log.i("TagInline","exef2Mid3. end.")
}
fun exeInlineNoInlineHighFunc(view: View) {
    Log.i("TagInline","exeInlineNoInlineHighFunc. start.")
    exef2Mid1()
    Log.i("TagInline","exeInlineNoInlineHighFunc. end.")
}

inline fun f2(p1: String, func1: () -> Unit, noinline func2: (String, String) -> Unit): (String, String) -> Unit {
    Log.i("TagInline","f2. step1")
    func1()
    Log.i("TagInline","f2. step2")
    func2(p1, p1)
    //内联函数中,函数类型参数继续当做对象使用,必须使用 noinline 进行修饰
    return func2
}

fun exef2() {
    Log.i("TagInline","exef2. start.")
    f2(
        "222",
        {
            Log.i("TagInline","exef2. func1")
            //只有内联函数的Lambda参数中,可以使用return
            //在Lambda参数中的return,会结束Lambda表达式的外部的外部的执行-》结束exef2的执行
            return
        },
        { s1, s2 ->
            Log.i("TagInline","exef2. func2")
        }
    )
    f2(
        "222",
        fun() {
            //在fun中的return,仅仅结束当前函数类型参数的执行
            Log.i("TagInline","exef2. func1")
            return
        },
        fun(s1: String, s2: String) {
            Log.i("TagInline","exef2. func2")
        }
    )
    Log.i("TagInline","exef2. end.")
}

2023-11-16 17:17:25.950 18931-18931 I/TagInline: exeInlineNoInlineHighFunc. start.
2023-11-16 17:17:25.950 18931-18931 I/TagInline: exef2Mid1. start.
2023-11-16 17:17:25.950 18931-18931 I/TagInline: exef2Mid2. start.
2023-11-16 17:17:25.950 18931-18931 I/TagInline: exef2Mid3. start.

2023-11-16 17:17:25.950 18931-18931 I/TagInline: exef2. start.
2023-11-16 17:17:25.950 18931-18931 I/TagInline: f2. step1
2023-11-16 17:17:25.950 18931-18931 I/TagInline: exef2. func1

2023-11-16 17:17:25.950 18931-18931 I/TagInline: exef2Mid3. end.
2023-11-16 17:17:25.950 18931-18931 I/TagInline: exef2Mid2. end.
2023-11-16 17:17:25.950 18931-18931 I/TagInline: exef2Mid1. end.
2023-11-16 17:17:25.950 18931-18931 I/TagInline: exeInlineNoInlineHighFunc. end.
inline及crossinline
  1. 在内联函数中间接调用函数类型参数,需要将该函数类型参数用 crossinline 修饰
    • 间接调用,比如在新的线程执行该函数类型参数
  2. 内联函数中被crossinline修饰的函数类型参数,其lambda表达式不再享有可以使用return的权利
inline fun f3(p1: String, crossinline func1: () -> Unit, noinline func2: (String, String) -> Unit): (String, String) -> Unit {
    Log.i("TagInline","f3. step1")
    func1()
    Log.i("TagInline","f3. step2")
    func2(p1, p1)
    //在内联函数中间接调用函数类型参数,需要将该函数类型参数用 crossinline 修饰
    GlobalScope.launch {
        func1()
    }
    return func2
}

fun exef3() {
    f3(
        "333",
        fun() {
            //在fun中的return,仅仅结束当前函数类型参数的执行
            Log.i("TagInline","exef3. func1")
            return
        },
        fun(s1: String, s2: String) {
            Log.i("TagInline","exef3. func2")
        }
    )
    f3(
        "333",
        {
            Log.i("TagInline","exef3. func1")
            //内联函数中被crossinline修饰的函数类型参数,其lambda表达式不再享有可以使用return的权利
            //return
        },
        { s1, s2 ->
            Log.i("TagInline","exef3. func2")
        }
    )
}

2023-11-16 17:17:31.425 18931-18931 I/TagInline: f3. step1
2023-11-16 17:17:31.426 18931-18931 I/TagInline: exef3. func1
2023-11-16 17:17:31.426 18931-18931 I/TagInline: f3. step2
2023-11-16 17:17:31.426 18931-18931 I/TagInline: exef3. func2

2023-11-16 17:17:31.448 18931-18931 I/TagInline: f3. step1
2023-11-16 17:17:31.448 18931-18931 I/TagInline: exef3. func1
2023-11-16 17:17:31.448 18931-18931 I/TagInline: f3. step2
2023-11-16 17:17:31.448 18931-18931 I/TagInline: exef3. func2

2023-11-16 17:17:31.450 18931-19120 I/TagInline: exef3. func1

2023-11-16 17:17:31.451 18931-19121 I/TagInline: exef3. func1

标准库中的扩展函数: run,let,with,also,apply,takeIf,takeUnless

run, let, with 返回值是lambda表达式最后一行
  • 若要返回lambda表达式最后一行,run最简洁
    var item:Item = Item()
    //run, let, with 返回值是lambda表达式最后一行
    var result1:String = item.run {
        a1 = "11"
        a2 = 11
        a3 = 11F
        "11111111111111"
    }
    var result2:Int = item.let {
        it.a1 = "222"
        it.a2 = 200
        it.a3 = 333F
        100
    }
    var result3:Float = with(item){
        a1 = "33"
        a2 = 33
        a3 = 333F
        3F
    }
also, apply 返回值是调用者自身
  • 若要返回调用者自身,apply最简洁
//also, apply 返回值是调用者自身
var result4:Item = item.also {
    it.a1 = "11"
    it.a2 = 11
    it.a3 = 11F
}
var result5:Item = item.apply {
    a1 = "11"
    a2 = 11
    a3 = 11F
}
takeIf 及 takeUnless
  1. takeIf : 过滤条件满足,则返回自身,不满足返回null
  2. takeUnless : 过滤条件不满足,则返回自身,满足返回null
//takeIf : 过滤条件满足,则返回自身,不满足返回null
item.takeIf { it.a2 > 100 }?.run {
    println(a2)
}
//takeUnless : 过滤条件不满足,则返回自身,满足返回null
item.takeUnless { it.a3 > 5F }?.run {
    println(a3)
}

Nothing的用法

Nothing用于标记该函数永不返回
  1. 仅仅用于抛出异常的的函数,返回值可以用Nothing
//仅仅用于抛出异常的的函数,返回值可以用Nothing
fun nothingf1(): Nothing {
    throw RuntimeException("nothingf1")
}
  1. 永远执行下去的函数,返回值可以用Nothing
//永远执行下去的函数,返回值可以用Nothing
fun nothingf2(): Nothing {
    while (true){
    }
}
Nothing是所有类型的子类型,Nothing可以作为泛型对象的临时空白填充
class CustomType<out T>{}

//Nothing是所有类型的子类型,Nothing可以作为泛型对象的临时空白填充
fun nothingCollection() {
    //Nothing可以创建空白集合实例,作为不同类型集合变量的初始值
    val emptyList: List<Nothing> = listOf()
    var list1: List<Int> = emptyList
    var list2: List<Float> = emptyList

    val emptySet: Set<Nothing> = setOf()
    var set1: Set<Int> = emptySet
    var set2: Set<Float> = emptySet

    val emptyMap: Map<String, Nothing> = mapOf()
    var map1: Map<String, Int> = emptyMap
    var map2: Map<String, Float> = emptyMap

    //除了集合,其他泛型也可以
    val emptyCustomType:CustomType<Nothing> = CustomType()
    var customType1:CustomType<Int> = emptyCustomType
    var customType2:CustomType<Float> = emptyCustomType
}

val关键字声明的变量只能赋值一次;

var关键字声明可更改的变量;

在输出语句中使用变量: ${变量名称}

var age = 365 * 8
println("You are already ${age}")
println("${age} is the very best age to celebrate!")

repeat创建循环语句: repeat(循环次数){}

repeat(10){
    print("傻狗")
}

使用 IntelliJ IDEA 运行 Hello world : www.web3.xin/code/2230.h…

  • 一开始犯的错,创建1个Kotlin的class,然后在里面写main函数.导致一直找不到run/运行 按钮.
  • 应该创建一个Kotlin文件,不是class,直接在里面写main函数,可以运行 使用Kotlin语言创建Java工程.png

IntRange : 用于表示一组Int值的范围. 从A到B.

var i1 = 1..6
var i2:IntRange = 1..6

fun showRandom(){
    println(i1.random())
    println(i2.random())
    //可以直接创建1个IntRange实例
    println((1..6).random())
}

如果一个类要被继承,可以使用 open 关键字进行修饰

open class BaseActivity : AppCompatActivity() {}

class Jump : BaseActivity() {}

Kotlin和Java代码互转

  1. Java文件转Kotlin文件
    • Code -> Convert Java File to Kotlin File
  2. Kotlin文件转Java文件
    • Tools -> Kotlin -> Show Kotlin ByteCode
    • Decompile

Class的写法

  1. Java中的Class实例,在Kotlin中写法是 ClassName::class.java
  2. Class实例作为方法参数,写法是 clazz:Class<*>
fun jump(clazz: Class<*>) {
    val intent = Intent(this, clazz)
    startActivity(intent)
}

fun J1(view: View) {
    val clazz = MainActivity::class.java
    jump(clazz)
}

静态函数

通过这个注解( @JvmStatic ), 将 object 和 companion object 的内部函数和属性,真正⽣生成为静态代码
open class T {
    companion object{
        @JvmStatic
        open fun f1():Unit{
        }
    }
}

如何定义静态常量

在 object 中用 const val 生命静态常量
object ObjectTest {
    const val a1:String = "111"
    open fun f1():Unit{
    }
}
//编译为Java代码
public final class ObjectTest {
   @NotNull
   public static final String a1 = "111";
   @NotNull
   public static final ObjectTest INSTANCE;

   private ObjectTest() {
   }

   static {
      ObjectTest var0 = new ObjectTest();
      INSTANCE = var0;
   }
}

如何定义静态函数

Kotlin实现静态方法与静态变量的两种方式
object ObjectTest {
    @JvmStatic
    open fun f1():Unit{
    }
}
//编译为Java代码
public final class ObjectTest {
   @NotNull
   public static final ObjectTest INSTANCE;
   @JvmStatic
   public static void f1() {
   }
   private ObjectTest() {
   }
   static {
      ObjectTest var0 = new ObjectTest();
      INSTANCE = var0;
   }
}

Elvis 操作符 : 可以通过 ?: 的操作来简化 if null 的操作

?: 后面可以是: 指定属性为null时候的缺省值, return, throw 1个异常
var date = lesson?.date ?: "星期一"
var date = lesson?.date ?: return
var date = lesson?.date ?: throw new Exception("***")

位运算符

Kotlin 运算符

Kotlin支持的位运算符同样有如下7个。

  1. and: 按位与。当两位同时为 1 时才返回 1。
  2. or: 按位或。只要有一位为 1,即可返回 1。
  3. inv: 按位非。单目运算符,将操作数的每个位(包括符号位)全部取反 。
  4. xor: 按位异或。当两位相同时返回 0,不同时返回 1。
  5. shl: 左移运算符。
  6. shr: 右移运算符。
  7. ushr: 无符号右移运算符。 Kotlin 的位运算符只能对 Int 和 Long 两种数据类型起作用。
首先左移和右移的区别是很好区分的

左移<< :就是该数对应二进制码整体左移,左边超出的部分舍弃,右边补零。举个例子:253的二进制码1111 1101,在经过运算253<<2后得到1111 0100。很简单

右移>> :该数对应的二进制码整体右移,左边的用原有标志位补充,右边超出的部分舍弃。

无符号右移>>> :不管正负标志位为0还是1,将该数的二进制码整体右移,左边部分总是以0填充,右边部分舍弃。

举例对比:
-5用二进制表示1111 1011,红色为该数标志位

-5>>2: 1111 1011-------------->1111 1110
11为原有标志位1补充得到

-5>>>2: 1111 1011-------------->0011 1110
左边部分总是以0填充 00为补充的0

循环控制

Kotlin 循环控制
for (int i = 1; i <= 10 ; i++) { }

for (int i = 1; i < 10 ; i++) { }

for (int i = 10; i >= 0 ; i--) { }

for (int i = 1; i <= 10 ; i+=2) { }

for (int i = 10; i >= 0 ; i-=2) { }

for (String item : collection) { }

for (Map.Entry<String, String> entry: map.entrySet()) { }

for (i in 1..10) { }

for (i in 1 until 10) { }

for (i in 10 downTo 0) { }

for (i in 1..10 step 2) { }

for (i in 10 downTo 0 step 2) { }

for (item in collection) { }

for ((key, value) in map) { }

字符串模板

  1. $字符串
  2. ${表达式(例如 if表达式)}
public fun f1():Unit{
    var a:String = "1"
    println("content is $a")
}
public fun f2():Unit{
    var a: Array<String>? = null
    println("content is ${if(a != null) a[0] else "a is null"}")
}

将很多特别小的类放到同一个文件中

c1.kt

open class c1{}
open class c2{}
open class c3{}

枚举类

  1. 在枚举类中定义任何方法,都要将枚举常量列表和方法定义用;分开
enum class Color1{
    RED, GREEN, BLUE;
    //在枚举类中定义任何方法,都要将枚举常量列表 和 方法定义 用 ; 分开
    fun f1():Boolean{
        return true
    }
}

enum class Color2(var r:Int, var g:Int, var b:Int){
    RED(255,0,0),
    GREEN(0,255,0),
    BLUE(0,0,255);
    //在枚举类中定义任何方法,都要将枚举常量列表 和 方法定义 用 ; 分开
    fun f1():Boolean{
        return true
    }
}
  1. 使用when处理枚举
fun gainColorStr(color:String) = 
    when(color){
        Color.RED -> "RED"
        Color.GREEN -> "GREEN"
        Color.BLUE -> "BLUE"
    }

when表达式

  1. when表达式带参数,则参数可以使用任何对象,when的分支条件也是对应类型对象
  2. when表达式不带参数,则when的分支条件就是任意的布尔表达式
fun whenFunc1(c1:Color, c2:Color) = 
    //when表达式可以使用任意类型对象做参数
    when(setOf(c1,c2)){
        //when的分支条件就是对应类型对象
        setOf(Color.RED, Color.GREEN) -> "c1"
        setOf(Color.RED, Color.BLUE) -> "c2"
        else -> "errorColor"
    }
    
fun whenFunc2(c1:Color, c2:Color) = 
    //when表达式不带参数
    when{
        (c1 == Color.RED && c2 == Color.GREEN) || (c1 == Color.GREEN && c2 == Color.RED) -> "color1"
        (c1 == Color.RED && c2 == Color.BLUE) || (c1 == Color.BLUE && c2 == Color.RED) -> "color2"
        else -> "errorColor"
    }
  1. 在when表达式中完成 类型检查 和 类型转换
  • kotlin使用is来进行类型检查
  • kotlin中已经通过is的类型检查,则可以直接按照对应类型访问参数
open interface IC2

class I1(val name: String) : IC2
class I2(var age: Int) : IC2

fun whenFunc4(i: IC2): Int =
    when (i) {
        //1: is 相当于Java中的instanceOf
        is I1 -> i.name.length
        //2: when中已经使用is做了判断,后面就可以直接将参数当做对应类型来使用
        is I2 -> i.age
        else -> -1
    }
  1. 使用代码块作为when的分支,则代码块最后一个表达式就是返回结果
fun whenFunc5(i:Int) = 
    when{
        i > 100 -> {
            Log.d("Tag","100")
            //返回结果是100
            100
        }
        i > 100 -> {
            Log.d("Tag","50")
            //返回结果是50
            50
        }
        else -> {
            Log.d("Tag", "else")
            //返回结果是0
            0
        }
    }

for循环遍历

  1. a..b 代表从a到b的闭合区间
    • a<b
  2. a until b 代表从a到b的半闭合区间,即不包括b
    • a<b
  3. a downTo b 代表从a到b的闭合区间
    • a>b
  4. step x
    • x>0 , 代表每次变化的量
fun funcFor1() {
    for (i in 1..10) {
        println("funcFor1: $i")
    }
}
fun funcFor2(){
    for(i in 1 until 10){
        println("funcFor2: $i")
    }
}
fun funcFor3(){
    for(i in 10 downTo 1){
        println("funcFor3: $i")
    }
}
fun funcFor4(){
    for (i in 1..10 step 2) {
        println("funcFor4: $i")
    }
}
fun funcFor5(){
    for (i in 10 downTo 1 step 2) {
        println("funcFor5: $i")
    }
}
fun funcFor6(){
    var arr:Array<Int> = arrayOf(4,3,6,0,17)
    for(i in arr){
        println("funcFor6: $i")
    }
}
fun funcFor7(){
    var map:HashMap<String,Int> = HashMap()
    map["a1"] = 3
    map["a2"] = 2
    map.put("a3",1)
    for ((k,v) in map){
        println("funcFor7. $k -- $v")
    }
}
fun funcFor8(){
    println("funcFor8. start.")
    for (i in 10 downTo -20 step 2) {
        println("funcFor8: $i")
    }
    println("funcFor8. end.")
}

使用 in 来检查一个值是否在某个区间, 使用 !in 来检查一个值是否不在某个区间.

  1. in检查一个值是否在某个区间
    • a in start..end
  2. !in检查一个值是否不在某个区间
    • a !in start..end
  3. in可以作为when的分支使用
    • when(p){in start..end -> {} else -> {}}
fun isLetter(c: Char): Boolean = c in 'a'..'z' || c in 'A'..'Z'
fun isNotDigit(c: Char): Boolean = c !in '0'..'9'
fun recognize(c: Char): String = when (c) {
    in '0'..'9' -> {
        Log.d("InTest","recognize. It's a digit")
        "It's a digit"
    }
    in 'a'..'z', in 'A'..'Z' -> {
        Log.d("InTest","recognize. It's a letter!")
        "It's a letter!"
    }
    else -> {
        Log.d("InTest","recognize. Error char!")
        "Error char!"
    }
}

try-catch

  1. Kotlin中,不会强制方法抛出指定异常.
  2. Kotlin中,try-catch是一个表达式,可以将它们赋值给一个变量.如果运行正常,变量的值就是try块中最后一行表达式,如果运行捕获到异常,变量的值就是catch块中最后一个表达式.
fun readNumber(reader:BufferedReader):Int{
    //在Java中,必须添加try-catch块捕获IOException,或方法本身抛出该异常,但Kotlin不会强制
    val line = reader.readLine()
    retutn Integer.parseInt(line)
}
fun readNumber2(reader:BufferedReader){
    val number = try{
        Log.d("TryTest","readNumber2. try.")
        //当执行无异常,则number赋值为最后一行表达式的值
        Integer.parseInt(reader.readLine())
    }catch(e:Exception){
        Log.d("TryTest","readNumber2. catch.")
        //当捕获到异常,则number赋值为321
        321
    }
    println(number)
}
D/TryTest: readNumber2. try.
D/TryTest: readNumber2. catch.
I/System.out: 321

在Kotlin中创建集合

  1. 创建集合形式: XXOf
  2. XXOf创建的集合,实际就是原始的Java集合类型
fun createCollection(view: android.view.View) {
    val r1 = setOf("1", "2", "3")
    val r2 = hashSetOf("1", "2", "3")
    val r3 = listOf(1, 2, 3)
    val r4 = arrayListOf(1, 2, 3)
    val r5 = arrayOf(1, 2, 3)
    val r6 = mapOf(1 to 2, 2 to 4, 3 to 6)
    val r7 = hashMapOf(1 to 2, 2 to 4, 3 to 6)
    Log.d(
        "CollectionTest",
        "r1:" + r1.javaClass + " ; r2:" + r2.javaClass + " ; r3:" + r3.javaClass + " ; r4:" + r4.javaClass + " ; r5:" + r5.javaClass + " ; r6:" + r6.javaClass + " ; r7:" + r7.javaClass
    )
}

//log
CollectionTest: r1:class java.util.LinkedHashSet ; r2:class java.util.HashSet ; r3:class java.util.Arrays$ArrayList ; r4:class java.util.ArrayList ; r5:class [Ljava.lang.Integer; ; r6:class java.util.LinkedHashMap ; r7:class java.util.HashMap

方法参数默认值 及 方法调用命名参数

  1. kotlin中声明方法,可以指定参数的默认值,对于指定了默认值的参数,调用方可以不传参数,直接使用默认值
    • 参数默认值最大的好处,是避免了过多的方法重载.
  2. kotlin中在调用方法时,可以显式地声明参数名称,可以改变参数的原始排序,也可以不传入具有默认值的参数
//p1, p2, p3 都声明了默认值
fun <T> joinToString(
    collection: Collection<T>,
    p1: String = ",",
    p2: String = "",
    p3: String = ""
): String {
    return "joinToString"
}

fun main() {
    //1:因为joinToString方法参数有默认值,所以可以只传入部分参数,
    //未传入的参数按照参数顺序使用方法声明的默认值
    joinToString(setOf(1, 2), "p1", "p2", "p3")
    joinToString(setOf(1, 2), ",")
    joinToString(setOf(1, 2))
    //2:如果在调用方法时,使用命名参数(显示地标明参数的名称),
    //则可以不按照方法参数顺序填入参数,也可以省略任意参数,直接使用方法声明的任意值
    joinToString(setOf(1, 2), p3 = "p3", p2 = "p2", p1 = "p1")
    joinToString(setOf(1, 2), p2 = "p2")
}

静态方法及静态属性

  1. 在Java中,我们经常定义一些工具类,包含很多public静态方法,也会写一些静态属性(静态变量或静态常量).
  2. 在kotlin中,只需要创建一个XX.kt的kotlin文件:
    • 在里面定义fun,定义的fun就属于静态方法.
    • 在里面声明var或val属性,就是静态属性.
      1. var a:Int = 1
      private static int a = 1;
      
      1. val a:Int = 1
      private static final int a = 1;
      
      1. const val a:Int = 1
      public static final int a = 1;
      
  3. 在XX.kt中定义的静态方法,在kotlin中调用,直接调用方法名称即可.
  4. 在XX.kt中定义的静态方法,在Java中调用
    • 原始形式是 XXKt.funcName
    • 若在XX.kt顶部添加注解@file:JvmName("FunctionUtils"),就可以改变kt文件生成的java类的名称.则Java中调用变成: FunctionUtils.joinToString 的形式.
      @file:JvmName("FunctionUtils")
      package com.transsion.kiaction.c3
      
      fun <T> joinToString(
          collection: Collection<T>,
          p1: String = ",",
          p2: String = "",
          p3: String = ""
      ): String {
          return "joinToString"
      }
      

扩展函数

  1. 扩展函数用于对指定类进行扩展,调用形式和该类普通成员函数一致.
  2. 扩展函数本质上是静态函数,不可重写
  3. 扩展函数的基本形式
    • ClassName称为接受者类型. 指定被扩展的类或接口
    • this称为接受者对象,指定类型的对象调用该扩展函数,该对象就是 this . this可以省略不写出来.
    fun ClassName.funcName(params):ReturnType{
        dealWith(this)
        return ReturnType
    }
    fun ClassName.funcName(params):ReturnType = ReturnType
    
  4. 不同文件可能定义同名扩展函数,如果在1个文件中引用了同名扩展函数,在import时候添加 as 设置别名
    import com.transsion.kiaction.c1.join as intJoin
    import com.transsion.kiaction.c3.join
    
    fun t(){
        var set1 = setOf("1","2","3")
        set1.join()
        set1.intJoin(200)
        var set2 = setOf(1)
        var r:Int = set2.intJoin()
    }
    

可变参数

  1. Java中可变参数使用 Type... 形式.
  2. Kotlin中可变参数使用 vararg 参数名称:参数类型 形式
  3. Kotlin中可变参数和Java中一样,也是一个数组,若fun的参数类型是vararg,则调用方必须传入已经解包的数组,即将每个数组元素作为单独的参数.
    • 比如一个变量本身已经是数组,则不可用传给参数类型是 可变参数的方法,必须对数组进行解包,使用 *数组 的形式.
fun f1VarArg(vararg params: String): Unit {}

fun f1(){
    var args:Array<String> = arrayOf("1","2","3")
    //对于数组,使用展开运算符 * ,将其中每个数组元素取出作为参数传入方法中.
    val list1 = listOf("100",*args)
    list1.forEach {
        Log.d("ExpandY","it:"+it)
        print(it)
    }
    val list2 = listOf("100",args)
    list2.forEach {
        Log.d("ExpandN","it:"+it)
        print(it)
    }
    //会报错
    //f1VarArg(args)
    //必须使用展开运算符* , 将原始数组解包后使用
    f1VarArg(*args)

    /*
    D/ExpandY: it:100
    D/ExpandY: it:1
    D/ExpandY: it:2
    D/ExpandY: it:3

    D/ExpandN: it:100
    D/ExpandN: it:[Ljava.lang.String;@79fe14
    * */
}

局部函数

  1. kotlin中,可以在函数内部继续声明函数,函数内部的函数称为局部函数.
  2. 局部函数可以访问其所在函数的所有参数.
class User(val name:String, val age:Int){}

fun localFunc(user:User):Unit{
    //local就是局部函数
    //local可以访问其所在函数:localFunc的所有参数
    fun local():Boolean{
        if(user.name==null){
            return false
        }
        return true
    }
    if(local()){
        Log.d("LocalFunc","经局部函数验证,参数合法!")
    }else{
        Log.d("LocalFunc","经局部函数验证,参数不合法!")
    }
}

lateinit : 延迟初始化

表示变量无法在声明的时候就初始化,但在使用前肯定会进行赋值.加了lateinit,该变量的初始化完全靠开发者保证.

lateinit var a1:Int

Kotlin中,函数类型也是一种数据类型,可以被继承

/**
 * Kotlin中,函数类型也是一种数据类型,可以被继承
 */
class IntegerFilter1(private val param: Float) : (Int) -> Boolean {
    override fun invoke(p1: Int): Boolean {
        return p1.isSmall(param) || p1.isTooSmall(param)
    }

    private fun Int.isSmall(param: Float): Boolean {
        return this < param.toInt() && inRange()
    }

    private fun Int.isTooSmall(param: Float): Boolean {
        return this < (param / 100F).toInt() && inRange()
    }

    private fun Int.inRange(): Boolean {
        return this in 100..300
    }
}

class IntegerFilter2(val p: Double) : (Int, Int) -> Boolean {
    override fun invoke(p1: Int, p2: Int): Boolean {
        return p1.f1(p) && p2.f1(p)
    }

    private fun Int.f1(param: Double): Boolean {
        return this > param.toInt()
    }
}

class IntegerFilter3 : (Int, Int) -> Boolean {
    override fun invoke(p1: Int, p2: Int): Boolean {
        return p1 > 100 && p2 > 100
    }
}

运算符重载

Kotlin中,可以使用operator声明运算符重载. 格式是:

class Type{
    operator fun 运算符(p1:Type1):Type2{
    }
}

实例:

class OperatorC1 {
    var a1: Int = 10
    operator fun plus(param: OperatorC1): OperatorC1 {
        val result = OperatorC1().apply {
            a1 += param.a1
        }
        return result
    }

    operator fun minus(param: OperatorC1): OperatorC1 {
        val result = OperatorC1().apply {
            a1 -= param.a1
        }
        return result
    }

    companion object {
        @JvmStatic
        fun main(args: Array<String>) {
            val p1 = OperatorC1()
            val p2 = OperatorC1()
            // + 对应着上面的plus
            val p3 = p1 + p2
            // - 对应着上面的minus
            val p4 = p1 - p2
        }
    }
}

Kotlin支持的运算符重载包括:

代码调用形式operator声明的名称
+aa.unaryPlus()
-aa.unaryMinus()
!aa.not()
a++a.inc()
a--a.dec()
a + ba.plus(b)
a - ba.minus(b)
a * ba.times(b)
a / ba.div(b)
a % ba.rem(b)
a .. ba.rangeTo(b)
a in bb.contains(a)
a !in b!b.contains(a)
a[i]a.get()
a[i, j]a.get(i, j)
a[i_1, ..., i_n]a.get(i_1, ..., i_n)
a[i] = ba.set(i, b)
a[i, j] = ba.set(i, j, b)
a[i_1, ..., i_n] = ba.set(i_1, ..., i_n, b)
a()a.invoke()
a(i)a.invoke(i)
a(i, j)a.invoke(i, j)
a(i_1, ..., i_n)a.invoke(i_1, ..., i_n)
a += ba.plusAssign(b)
a -= ba.minusAssign(b)
a *= ba.timesAssign(b)
a /= ba.divAssign(b)
a %= ba.remAssign(b)
a == ba?.equals(b) ?: (b === null)
a != b!(a?.equals(b) ?: (b === null))
a > ba.compareTo(b) > 0
a < ba.compareTo(b) < 0
a >= ba.compareTo(b) >= 0
a <= ba.compareTo(b) <= 0

运算符重载