Kotlin基础知识学习笔记

1、当一个函数中只有一行代码时,Kotlin允许我们不必编写函数体,可以直接将唯一的一行代码写 在函数定义的尾部,中间用等号连接即可

例:

fun largerNumber(num1: Int, num2: Int): Int {
    return max(num1, num2)
}

简写 ->

fun largerNumber(num1: Int, num2: Int): Int = max(num1, num2)

2、if语句相比于Java有一个额外的功能,它是可以有返回值的,返回值就是if语句每一个条件中最后一行代码的返回值

例:

fun largerNumber(num1: Int, num2: Int): Int {
        var value = 0
        if (num1 > num2) {
            value = num1
        } else {
            value = num2
        }
        return value
    }

简写 ->

fun largerNumber(num1: Int, num2: Int): Int {
        val value = if (num1 > num2) {
            num1
        } else {
            num2
        }
        return value
    }

3、when关键字的使用规则

fun getScore(name: String) = when (name) {
            "Tom" -> 86

            "Jim" -> 77

            "Jack" -> 95

            "Lily" -> 100
        else -> 0
    }

或者类型匹配

fun checkNumber(num: Number) {
        when (num) {
            is Int -> println("number is Int")

            is Double -> println("number is Double")

            else -> println("number not support")
        }
    }

4、for-in循环

例:

升序循环 ..和until关键字

val range = 0..10         表示[0, 10]都是闭区间

fun main() {
        for (i in 0..10) {
            println(i)
        }
    }

输出结果:0,1,2,3,4,5,6,7,8,9,10

val range = 0 until 10    表示[0, 10)左闭右开

step关键字 表示间隔多少

 fun main() {
        for (i in 0 until 10 step 2) {
            println(i)
        }
    }

输出结果:0,2,4,6,8

降序循环 downTo关键字

fun main() {
        for (i in 10 downTo 1) {
            println(i)
        }
    }

输出结果:10,9,8,7,6,5,4,3,2,1

5、可见性修饰符对照表

6、数据类,单例类

  1. 数据类:当在一个类前 面声明了data关键字时,就表明你希望这个类是一个数据类,Kotlin会根据主构造函数中的参 数帮你将equals()、hashCode()、toString()等固定且无实际逻辑意义的方法自动生成。
  2. 单例类:在Kotlin中创建一个单例类的方式极其简单,只需要将class关键字改成object关键字即可,在Kotlin中我们不需要私有化构造函数,也不需要提供getInstance()这样的静态 方法,只需要把class关键字改成object关键字,一个单例类就创建完成了

7、Lambda编程

7.1 集合的创建与遍历(List,Set,Map)

List: listOf()函数,初始化之后就不可变

val list = listOf("Apple", "Banana", "Orange", "Pear", "Grape")

mutableListOf()函数初始化之后还可以变

例:

fun main() {

        val list = mutableListOf("Apple", "Banana", "Orange", "Pear", "Grape")

        list.add("Watermelon")

        for (fruit in list) {

            println(fruit)
        }

    }

Set:和List的使用几乎一模一样,setOf(),mutableSetOf()函数

只是Set集合中是不可以存放重复元素的,如果存放了多个相同的元素,只会保留其中一 份。

Map:mapOf(),mutableMapOf()

例:

val map = mapOf("Apple" to 1, "Banana" to 2, "Orange" to 3)

注意:这里的to不是关键字,而是一个infix函数

map集合的遍历,示例如下:

fun main() {

        val map = mapOf("Apple" to 1, "Banana" to 2, "Orange" to 3)
        
        for ((fruit, number) in map) {

            println("fruit is $fruit, number is $number")

        }
    }

7.2集合的函数式API

Lambda定义:Lambda就是一小段可以作为参数传递的代码

语法结构:{参数名1: 参数类型, 参数名2: 参数类型 -> 函数体}

首先最外层是一对大括号,如果有参数传入到 Lambda表达式中的话,我们还需要声明参数列表,参数列表的结尾使用一个->符号,表示参 数列表的结束以及函数体的开始,函数体中可以编写任意行代码(虽然不建议编写太长的代 码),并且最后一行代码会自动作为Lambda表达式的返回值。

例:

val list = listOf("Apple", "Banana", "Orange", "Pear")

val lambda = { fruit: String -> fruit.length }

val maxLengthFruit = list.maxBy(lambda)

val maxLengthFruit =  list.maxBy({ fruit: String -> fruit.length })

 

Kotlin规定,当Lambda参数是函数的最后一个参数时,可以将Lambda表达式移到函数括 号的外面

val maxLengthFruit = list.maxBy() { fruit: String -> fruit.length }

如果Lambda参数是函数的唯一一个参数的话,还可以将函数的括号省略

val maxLengthFruit = list.maxBy { fruit: String -> fruit.length }

Lambda表达式中的参数列表其实在大多数情况下不必声明参数类型

val maxLengthFruit = list.maxBy { fruit -> fruit.length }

当Lambda表达式的参数列表中只有一个参数时,也不必声明参数名,而是可以使用it 关键字来代替

val maxLengthFruit = list.maxBy { it.length }

7.3 其他函数式API
①filter函数
   fun main() {

        val list = listOf("Apple", "Banana", "Orange", "Pear")
        val newList = list.filter { it.length <= 5 }.map { it.toUpperCase() }

        for (fruit in newList) {

            println(fruit)

        }

    }

想保留5个字母以内的水果,使用以上filter函数来实现

②any和all函数

any函数用于判断集 合中是否至少存在一个元素满足指定条件 all函数用于判断集合中是否所有元素都满足指定条件

 fun main() {

        val list = listOf("Apple", "Banana", "Orange", "Pear")

        val anyResult = list.any { it.length <= 5 }

        val allResult = list.all { it.length <= 5 }
        println("anyResult is $anyResult, allResult is $allResult")

    }

8、匿名类

由于Kotlin完全舍弃了new关键字,因此创建匿名类 实例的时候就不能再使用new了,而是改用了object关键字。

   fun runTread() {
        Thread(object : Runnable {

            override fun run() {

                println("Thread is running")

            }

        }).start()
    }

简写 -> 

fun openThread() {
        Thread(Runnable {

            println("Thread is running")

        }).start()
    }

简写 ->

    fun openThread() {
        Thread({

            println("Thread is running")

        }).start()
    }

简写 -> 

Thread { println("Thread is running") }.start()

9、空指针检查

student:Student?   ?表示student这个对象可为空

student?.play()     ?.表示student这个对象不为空才调用play()方法

student.length()?:0   符号左边表达式不为空就返回左边,否则返回右边的值

student!!.play()    告诉编译器student对象不可能为空,强行通过编译

10、标准函数

① let函数

这个函数提供了函数式API的编程接口,并将原始调用对象作为参数传递到 Lambda表达式中

语法结构:

obj.let {

obj2 -> // 编写具体的业务逻辑

 }

这里调用了obj对象的let函数,然后Lambda表达式中的代码就会立即执行,并且 这个obj对象本身还会作为参数传递到Lambda表达式中。不过,为了防止变量重名,这里我将 参数名改成了obj2,但实际上它们是同一个对象

例:

fun doStudy(study: Study?) {

        study?.let { stu ->

            stu.readBooks()

            stu.doHomework()

        }

    }

前面所讲的Lambda表达式的语法特性当Lambda表达式的参数列表中只有一个参数时, 可以不用声明参数名,直接使用it关键字来代替即可,那么代码就可以进一步简化成

    fun doStudy(study: Study?) {
        study?.let {
            it.readBooks()
            it.doHomework()
        }
    }
② with函数

with函数接收两个参数:第一个参数可以是一个任意类型的对 象,第二个参数是一个Lambda表达式。with函数会在Lambda表达式中提供第一个参数对象 的上下文,并使用Lambda表达式中的最后一行代码作为返回值返回

    fun testWith() {
        val list = listOf("Apple", "Banana", "Orange", "Pear", "Grape")

        val builder = StringBuilder()

        builder.append("Start eating fruits.\n")

        for (fruit in list) {

            builder.append(fruit).append("\n")

        }

        builder.append("Ate all fruits.")

        val result = builder.toString()

        println(result)
    }

简写 -> 

fun testWith() {
        val list = listOf("Apple", "Banana", "Orange", "Pear", "Grape")

        val result = with(StringBuilder()) {

            append("Start eating fruits.\n")

            for (fruit in list) {

                append(fruit).append("\n")

            }

            append("Ate all fruits.")

            toString()

        }

        println(result)
    }

③ run函数

run函数。run函数的用法和使用场景其实和 with函数是非常类似的,只是稍微做了一些语法改动而已。首先run函数通常不会直接调用, 而是要在某个对象的基础上调用;其次run函数只接收一个Lambda参数,并且会在Lambda表 达式中提供调用对象的上下文。其他方面和with函数是一样的,包括也会使用Lambda表达式 中的最后一行代码作为返回值返回

    fun testRun() {
        val list = listOf("Apple", "Banana", "Orange", "Pear", "Grape")

        val result = StringBuilder().run {

            append("Start eating fruits.\n")

            for (fruit in list) {

                append(fruit).append("\n")

            }

            append("Ate all fruits.")

            toString()

        }

        println(result)
    }

④ apply函数

apply函数和run函数也是极其类似的,都要在某 个对象上调用,并且只接收一个Lambda参数,也会在Lambda表达式中提供调用对象的上下 文,但是apply函数无法指定返回值,而是会自动返回调用对象本身

    fun testApply() {
        val list = listOf("Apple", "Banana", "Orange", "Pear", "Grape")

        val result = StringBuilder().apply {

            append("Start eating fruits.\n")

            for (fruit in list) {

                append(fruit).append("\n")

            }

            append("Ate all fruits.")

        }

        println(result.toString())
    }

11、companion object

Kotlin规定,所有定义在companion object中的方法都可以使用类似于Java静态方法的形式调用。

    class Util {

        fun doAction1() {

            println("do action1")

        }

        companion object {

            fun doAction2() {

                println("do action2")

            }

        }

    }

companion object这个关键字实际上会 在Util类的内部创建一个伴生类,而doAction2()方法就是定义在这个伴生类里面的实例方 法。只是Kotlin会保证Util类始终只会存在一个伴生类对象,因此调用Util.doAction2()方 法实际上就是调用了Util类中伴生对象的doAction2()方法

只有在单例类、companion object或顶层方法中才可以使用const关键字

12、定义静态方法

①在companion object代码块里面定义方法,但是并不是真正意义上的静态方法(解释见知识点11)

②在cmpanion object代码块里面定义的方法上面添加注解@JvmStatic

③Kotlin编译器会将所有顶层方法编译成静态方法,创建文件类型为file的Kotlin的文件,直接在这个文件里面定义方法

13、延迟初始化和密封类

延迟初始化关键字 lateinit

例:private lateinit var adapter: MsgAdapter

这里就可以不用立即对adapter初始化,而且MsgAdapter后面不需要加?

密封类关键字 sealed 

例:

sealed class Result

class Success(val msg: String) : Result()

class Failure(val error: Exception) : Result()
   
fun getResultMsg(result: Result) = when (result) {

        is Success -> result.msg

        is Failure -> "Error is ${result.error.message}"

    }

在when语句块里不需要写else的情况了,这是因为当在when语句中传入一个密封类变量 作为条件时,Kotlin编译器会自动检查该密封类有哪些子类,并强制要求你将每一个子类所对应 的条件全部处理。这样就可以保证,即使没有编写else条件,也不可能会出现漏写条件分支的 情况。而如果我们现在新增一个Unknown类,并也让它继承自Result,此时 getResultMsg()方法就一定会报错,必须增加一个Unknown的条件分支才能让代码编译通 过。

14、扩展函数

相比于定义一个普通的函数,定义扩展函数只需要在函数名的前面加上一个ClassName.的语 法结构,就表示将该函数添加到指定类当中了

语法:

    fun ClassName.methodName(param1: Int, param2: Int): Int {

        return 0

    }

例:

    object StringUtil {

        fun lettersCount(str: String): Int {

            var count = 0

            for (char in str) {

                if (char.isLetter()) {

                    count++

                }

            }

            return count

        }

    }

使用这个方法

val str = "ABC123xyz!@#"

val count = StringUtil.lettersCount(str)

使用扩展函数来改编

    fun String.lettersCount(): Int {

        var count = 0

        for (char in this) {

            if (char.isLetter()) {

                count++

            }

        }

        return count

    }

使用方式:

val count = "ABC123xyz!@#".lettersCount()

15、高阶函数

如果一个函数接收另一个函数作为参数,或者返回值的类型是 另一个函数,那么该函数就称为高阶函数

例:

fun example(func: (String, Int) -> Unit) {

func("hello", 123)

}

16、内联函数的局限性

内联的函数类型参数在编译的时候会被进行代码替换,因此它没有真正 的参数属性。非内联的函数类型参数可以自由地传递给其他任何函数,因为它就是一个真实的 参数,而内联的函数类型参数只允许传递给另外一个内联函数,这也是它最大的局限性

另外,内联函数和非内联函数还有一个重要的区别,那就是内联函数所引用的Lambda表达式 中是可以使用return关键字来进行函数返回的,而非内联函数只能进行局部返回

如果我们在高阶函数中创建了另外的Lambda或者匿名类的实现,并且在这些实现 中调用函数类型参数,此时再将高阶函数声明成内联函数,就一定会提示错误,因为内联函数的Lambda表达式中允许使用return关键字,和高阶函数的匿名类实 现中不允许使用return关键字之间造成了冲突

17、infix函数

① infix函数是 不能定义成顶层函数的,它必须是某个类的成员函数,可以使用扩展函数的方式将它定义到某 个类当中;

② infix函数必须接收且只能接收一个参数,至于参数类型是没有限制的。

infix fun String.beginsWith(prefix: String) = startsWith(prefix)

if ("Hello Kotlin" beginsWith "Hello") {

// 处理具体的逻辑

}

18、泛型

所有基于JVM的语言,它们的泛型功能都是通过类型擦除机制来实现的,其中当然也包括了 Kotlin。这种机制使得我们不可能使用a is T或者T::class.java这样的语法,因为T的实际 类型在运行的时候已经被擦除了。

但是在Kotlin提供了一个内联函数的概念,内联函数中的代码会在编译的时候自动被替换到调用它的地方,这样的话也就不存 在什么泛型擦除的问题了,因为代码在编译之后会直接使用实际的类型来替代内联函数中的泛 型声明

① 泛型实化

首先,该函数必须是内联函数才行,也就是要用inline 关键字来修饰该函数。

其次,在声明泛型的地方必须加上reified关键字来表示该泛型要进行 实化。语法如下:

inline fun <reified T> getGenericType() {

}

示例代码:

    inline fun <reified T> getGenericType() = T::class.java



    fun main() {

        val result1 = getGenericType<String>()

        val result2 = getGenericType<Int>()

        println("result1 is $result1")

        println("result2 is $result2")

    }

② 泛型协变

③ 泛型逆变

18、协程

添加依赖:

implementation

"org.jetbrains.kotlinx:kotlinx-coroutines-core:1.1.1"

implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.1.1"

Global.launch函数

Global.launch函数每次创建的都是一个顶层协程,这种协程当应用程序运行结束 时也会跟着一起结束。

    fun main() {

        GlobalScope.launch {

            println("codes run in coroutine scope")

        }

    }
runBlocking函数

runBlocking函数同样会创建一个协程的作用域,但是它可以保证在协程作用域内的所有代码 和子协程没有全部执行完之前一直阻塞当前线程。

注意runBlocking函数通常只应该在测试环境下使用,在正式环境中使用容易产生一些性能上的问题。

launch函数

创建多个协程,它和GlobalScope.launch函数不同。

首先它必须在 协程的作用域中才能调用,

其次它会在当前协程的作用域下创建子协程。

子协程的特点是如果 外层作用域的协程结束了,该作用域下的所有子协程也会一同结束

coroutineScope函数

coroutineScope函数也是一个挂起函数, 因此可以在任何其他挂起函数中调用。它的特点是会继承外部的协程的作用域并创建一个子协程,它可以保证其作用域内的所有代码和子协程在全部执行完之前,外部的协程会一直被挂起

    suspend fun printDot() = coroutineScope {

        launch {

            println(".")

            delay(1000)

        }

    }

总结:

GlobalScope.launch和runBlocking函数是可以在任意地方调用的, coroutineScope函数可以在协程作用域或挂起函数中调用

launch函数只能在协程作用域中调用

async函数

async函数必须在协程作用域当中才能调用,它会创建一个新的子协程并返回一个Deferred对象,如果我们想要获取async函数代码块的执行结果,只需要调用Deferred对象的await() 方法即可。示例代码如下:

    fun main() {

        runBlocking {

            val result = async {

                5 + 5

            }.await()

            println(result)

        }

    }

小技巧:要使用到返回值的时候再通过async函数返回的对象Deferred对象再去调用await()得到返回值,这样多个async函数就可以并行了

⑥ withContext()函数

它跟async函数差不多,调用这个函数后会立即执行代码块里面的代码,同时将外部协程挂起,最后一行作为返回值返回,只不过它强制要求传入一个线程参数

示例代码:

    fun main() {

        runBlocking {

            val result = withContext(Dispatchers.Default) {

                5 + 5

            }

            println(result)

        }

    }

线程参数主要值:Dispatchers.Default、Dispatchers.IO和 Dispatchers.Main

Dispatchers.Default:表示会使用一种默认低并发的线程策略,当 你要执行的代码属于计算密集型任务时,开启过高的并发反而可能会影响任务的运行效率

Dispatchers.IO:表示会使用一种较高并发的线程策 略,当你要执行的代码大多数时间是在阻塞和等待中,比如说执行网络请求时,为了能够支持 更高的并发数量就可以使用它

Dispatchers.Main:在Android主线程执行(也只能在Android中用)

suspendCoroutine函数

suspendCoroutine函数必须在协程作用域或挂起函数中才能调用,它接收一个Lambda表达 式参数,主要作用是将当前协程立即挂起,然后在一个普通的线程中执行Lambda表达式中的 代码。Lambda表达式的参数列表上会传入一个Continuation参数,调用它的resume()方 法或resumeWithException()可以让协程恢复执行

  • 21
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值