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、可见性修饰符对照表
![](https://i-blog.csdnimg.cn/direct/07105e9b766b496792b7af79a1f5fde1.png)
6、数据类,单例类
- 数据类:当在一个类前 面声明了data关键字时,就表明你希望这个类是一个数据类,Kotlin会根据主构造函数中的参 数帮你将equals()、hashCode()、toString()等固定且无实际逻辑意义的方法自动生成。
- 单例类:在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()可以让协程恢复执行