【Kotlin】从Java转向Kotlin,耐心看完这篇博客就够了。Kotlin快速入门教程分享

导读

适合群体:Java已经入门的人,如果是零基础,不要勉强!虽然没有深奥的术语,即使有也尽可能通俗易懂 。

Kotlin和Java都是Jvm语言,相同的部分能省则省(篇幅有限),重点是Kotlin。

示例代码的注释很重要。最好可以使用IDEA等开发工具运行一下。

最后创作不易,全部都是自己亲手码出来的5万多字的笔记分享,如果觉得对您学习Kotlin有帮助,还请三连(点赞,关注,打赏),这是我的创作动力。

build.gradle.kts

//version_gradle:7.4.2
//version_jdk:17.0.6
plugins {
    
    
    kotlin("jvm") version "1.8.0"
    application
}

repositories {
    
    
    mavenLocal()
    mavenCentral()
}

dependencies {
    
    
    testImplementation(kotlin("test"))
    implementation("org.jetbrains.kotlin:kotlin-reflect:${
      
      kotlin.coreLibrariesVersion}")
    implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.6.4")
}

tasks.test {
    
    
    useJUnitPlatform()
}

tasks.withType<JavaCompile> {
    
    
    options.encoding = "UTF-8"
}

kotlin {
    
    
    jvmToolchain(17)
}

application {
    
    
    mainClass.set("MainKt")
}

目录

基础篇

启动入口

main

fun main(args: Array<String>) {
    
    
	println("Hello World")
}

无参数的main方法与有参数传入的main方法都是kotlin程序启动的入口方法。
启动时先调用有参的main方法,然后调用无参的main方法。

fun main() {
    
    
	println("Hello World")
}

虽然两个main方法可以同时存在,无形参的main方法不会执行。
在这里插入图片描述

注释

Kotlin的注释与Java一致

// 单行注释
/*
多行注释
*/
/**
文档注释,KDoc,详见:dokka
 */

方法

方法默认为不可继承的静态方法,参数列表传入的值不能为null

//关键字 fun
//形参名: 类型
//fun 方法名(参数列表): 返回值类型 {方法体}
fun add(n1: Int, n2: Int): Int {
    
    
	return n1 + n2
}

add方法对应Java的方法如下。

public static final int add(int n1, int n2) {
    
    
	return n1 + n2;
}

如果需要去掉final(子类继承和访问),可以使用关键字open修饰fun

class Main {
    
    
    open fun add(n1: Int, n2: Int): Int {
    
    
        return n1 + n2
    }
}

方法只有一行时,可以使用=替代{...}

fun add(n1: Int, n2: Int): Int = n1 + n2

数据类型

变量、常量

//变量
var i: Int = 1
//常量
val n: Int = 2

i = i + 1 //OK
n = n + 1 //ERROR, Val cannot be reassigned

原始数据类型

Kotlin没有基本数据类型,只有封装Java基本类型后的原始数据类型。

编译前都是包装的类型,编译时会进行判断(null的可能性检查),选择基本类型还是包装类型。

整数

类型 位宽 最小值 最大值
Byte 8 -128 127
Short 16 -32768 32767
Int 32 -2,147,483,648 2,147,483,647
Long 64 -9,223,372,036,854,775,808 9,223,372,036,854,775,807
  • 整型默认为Int

  • Long类型的整数需要加尾缀L(必须大写)

浮点

类型 位宽 有效位数 指数位数 最小值 最大值
Float 32 24 8 1.4e-45 3.4028235e38
Double 64 53 11 4.9e-324 1.7976931348623157e308
  • 浮点型默认为Double
  • Float需要加尾缀F
  • e标记的前后必须为10进制数,2e3表示2*10^3,不支持Java的p标记

字符

字符 位宽 精度范围
Char 16 \u0000~\uFFFF

Char类型废弃了所有转换为数字的方法。

var a: Char
var b: Int

a = 'A'
//Int -> Char
a = (65).toChar()
a = Char(65)
//Char -> Int
a = 'A'
b = a.code

布尔

布尔类型 范围
Boolean false, true

无符号数字

类型 位宽 最大值
UByte 8 255
UShort 16 65535
UInt 32 4,294,967,295
ULong 64 18,446,744,073,709,551,615
var a: ULong
var b: UByte

a = 200UL
//Sign -> UnSign
b = (-10).toUByte() //246
//UnSign -> Sign
println(b.toByte()) //-10
//UnSign Calc
var c = a * b
println(c)//200*246=49200
  • 无符号类型需要加尾缀U
  • 无符号类型的变量不能直接与有符号类型的变量运算

进制

默认为10进制,16进制前缀为0x,2进制前缀为0b,不支持8进制。

数字和数字之间可以使用下划线_分隔。

数组

ArrayIntArrayUIntArray

字符串

String"字符串""""原始字符串"""

数值类型的自动转换

原数据类型的精度范围在目标数据类型的范围内,可以自动转换。否则会因为丢失精度转换为错误的数值。

//Long转Int不能强制转换
var a: Int = 129L; //ERROR
//Int转Long自动转换
var b: Long = 129;
//超过最大值,最高位的进位丢失
var c = b.toByte() //-127

不同类型进行运算时,先自动转换为范围较大的类型,然后再运算(相同的类型才能进行运算)。

var a: Int = 129;
var b: Long = 129L;
var c = a * b //Long
println(c) //16641

表达式和语句

//这是条语句,其中 n = 1 是表达式
var n = 1

表达式有返回值,语句没有(等价于Java的Void)。

语句是程序的执行最小单元。

var n //声明语句
n = 1 //赋值语句(表达式加换行符)
n++ //自增语句
println(n) //方法调用语句

还有创建对象语句、控制流语句。

控制流语句

不支持goto

if

用法

var a = 1
var b = 2
var max = a
//单行写法
if (a < b) max = b
//完整的写法
if (a > b) {
    
    
    max = a
} else if (a < b) {
    
    
    max = b
} else {
    
    
    TODO("一样大,什么也不做")
}
//表达式写法
max = if (a < b) b //ERROR
max = if (a > b) a else b //OK
max = if (a > b) {
    
     //OK
    println("Choose a")
    a //缺省 return 的返回值
} else {
    
    
    println("Choose b")
    b
}
println(max)

when

类似switch的用法,Java14、17、18都进行了加强,现在Java的switch和Kotlin的when用法相似。

val n = when (val s = readlnOrNull()) {
    
    
    null -> 0
    else -> s.length
}
println("输入了${
      
      n}个字符")

使用Range进行匹配。

fun main() {
    
    
    test(2)
}

fun test(v: Int) {
    
    
    when (v) {
    
    
        in 1..5 -> {
    
    
            println("工作日")
        } //不用担心break
        in setOf(6, 7) -> println("休息日")
        !in 1..7 -> {
    
    
            println("非法输入")
        }
    }
}

使用Set集合进行匹配,原理是比较HashCode值,所以Set集合是无序。

fun main() {
    
    
    test("绿", "红") //黄
}

fun test(a: String, b: String) {
    
    
    when (setOf(a, b)) {
    
    
        setOf("红", "绿") -> {
    
    
            println("黄")
        }
        setOf("红", "蓝") -> {
    
    
            println("紫")
        }
        setOf("蓝", "绿") -> {
    
    
            println("青")
        }
        else -> {
    
    
            println("非法输入")
        }
    }
}

数据类型匹配

fun main() {
    
    
    test(2)
}

fun test(v: Any) = when (v) {
    
    
    is String -> println("字符串")
    is Int -> println("数字")
    else -> println("未知")
}

for

//.. 正序:[0, 10]
for (i in 0..10) {
    
    
    //0 1 2 3 4 5 6 7 8 9 10
    print("$i ")
    //$ 字符串模板,格式化输出变量的值
}
//until 正序:[0, 10)
for (i in 0 until 10) {
    
    
    //0 1 2 3 4 5 6 7 8 9
    print("$i ")
}
//downTo 逆序:[10, 0]
//step 步进值
for (i in 10 downTo 0 step 3) {
    
    
    //10 7 4 1
    print("$i ")
}

forEach

val a: IntProgression = (10 downTo 0 step 3)
a.forEach {
    
     i -> //这里不写,默认形参为it
    //10 7 4 1
    print("$i ")
}

dowhile

用法

while (/*布尔表达式*/true) {
    
    
    TODO("循环内容")
}
do {
    
    
    TODO("循环内容")
} while (/*布尔表达式*/true)

label@

跳出到指定代码块的标签

val gcd = lambda@{
    
    
    w1@ while (true) {
    
    
        while (true) {
    
    
            break
            continue@w1
            return@lambda
        }
    }
}

null

Type?

任何数据类型后面加?,表示这个类型的值可以为null

var a: Int? //可以为null
var b: Int //不可以为null

可以为null的类型不可以赋值给不能为null的类型

fun main() {
    
    
    var a: Int? = 1
    test1(a) //Int? 不可以向 Int 传参
    var b: Int = 1
    test2(b) //Int 可以向 Int? 传参
}

fun test1(a: Int) {
    
    
    println(a)
}

fun test2(a: Int?) {
    
    
    println(a)
}

可能为null的方法调用

var str: String? = null
//必须加?,返回值类型为Int?
var len = str?.length
println(len) //null

?:

可以为null的数据类型,可以使用Elvis操作符。

var str: String? = null
//左边为null时,执行右边
var len = str?.length ?: -1
println(len) //-1

as?

var str = "123"
//左边不为null时,强制转换为右边的类型
//不支持从父类强制转换为子类
//转换失败,结果为null
var b = str as? String
println(b) //123

!!

var str = "123"
//str必须为非null,否则抛出
//java.lang.NullPointerException
var b = str!!.length
println(b) //3

尽量避免使用非null断言(!!),会破坏kotlin对null的设计初衷。

var str: String? = "123"
str?.let {
    
    
//let:只有在str非null时执行
//避免直接调用str.length出现异常
//也避免了执行str?.length,出现不正常的结果
//it是默认传入的参数,不为null
    println(it.length) //3
}

或者直接退出,不执行后续代码,保证不产生不正常的结果和执行多余的步骤。

var str: String? = "123"
//如果为null,执行右边的代码退出(或者抛异常)
var s = str ?: return
println(s?.length) //3
var str: String? = "123"
//使用?:避免null引发后续代码的执行
//如果str为null,会出现非法值-1,不会进入执行
if ((str?.length ?: -1) >= 0) {
    
    
    println(str?.length) //3
}

包装类型和基本类型

编译时如果参数有?修饰,并且有null的可能,编译后为装箱的包装类型,否则会自动优化,都编译为基本数据类型。

//int
var a: Int = 1
//Integer
var b: Int? = null

集合和数组默认使用Java的包装类型,如果使用特定类型的arrayOf方法,则是Java的基本类型。

//List<Integer>
var list: List<Int> = listOf(1, 2, 3)
//Integer[]
//Array<T>的arrayOf方法
var arr1: Array<Int> = arrayOf(1, 2, 3)
//int[]
var arr2 = intArrayOf(1, 2, 3)

访问修饰符

修饰类和顶层成员

修饰符 Java最大访问范围 Kotlin最大访问范围
public 全局 全局
protected 相同包和所有子包 不支持
internal 不支持 相同模块
default、缺省 相同包 全局
private 源文件 源文件

修饰类的成员

修饰符 Java最大访问范围 Kotlin最大访问范围
public 全局 全局
protected 相同包和所有子包 所有子类
internal 不支持 相同模块
default、缺省 相同包 全局
private

访问权限的传递

继承访问权限时,访问修饰符的访问范围不能被放大,可以更严格(缩小可以访问的范围)。

数组

创建

fun main() {
    
    
    //Integer[]
    val arr1: Array<Int> = Array(3) {
    
     1 }
    println(arr1.contentToString())//[1, 1, 1]

    val arr2: Array<Int> = arrayOf(1, 2, 3)
    println(arr2.contentToString())//[1, 2, 3]
    //int[]
    val arr3: IntArray = IntArray(3) {
    
     1 }
    println(arr3.contentToString())//[1, 1, 1]
    val arr4: IntArray = intArrayOf(1, 2, 3)
    println(arr4.contentToString())//[1, 2, 3]
    //Integer[][]
    //IntArray只能是一维数组
    val arr5 = Array(3) {
    
     intArrayOf(0, 0, 0) }
    println(arr5.contentDeepToString())//[[0, 0, 0], [0, 0, 0], [0, 0, 0]]
}

常用方法

map

将每一个元素操作后放回。

val arr = intArrayOf(1, 2, 3)
arr.map {
    
    
    it * 2
}.also {
    
    
    println(it) //[2, 4, 6]
}

flatMap

将每一个元素操作后放入一个集合,然后操作下一个元素时,将这个集合中的所有元素整合在一起,最后返回整合后的集合。

val arr = intArrayOf(1, 2, 3)
arr.flatMap {
    
    
    listOf(it * 2, 0)
}.also {
    
    
    println(it) //[2, 0, 4, 0, 6, 0]
}

fold

设置初始值,然后进行累计操作。

val arr = intArrayOf(1, 2, 3)
//初始值1,累加
arr.fold(1) {
    
     sum, i ->
    sum + i
}.also {
    
    
    println(it) //7
}

associate

将数组的元素转换为键值对,键相同时会不存放。

val arr = intArrayOf(1, 2, 3)
arr.associate {
    
    
    Pair(it, it * 2) //键值对
}.also {
    
    
    println(it) //{1=2, 2=4, 3=6}
}

//另一种写法,以数组的元素为键,存放特定的值,键相同时会不存放。
intArrayOf(1, 2, 3).associateWith {
    
     it * 2 }.also(::println)

将数组的元素按照特定的键进行存放,键相同时会不存放。

val arr = intArrayOf(1, 2, 3)
arr.associateBy {
    
    
    it * 2
}.also {
    
    
    println(it) //{2=1, 4=2, 6=3}
}

distinct

去重。已经存在时不存放。

val arr = intArrayOf(1, 2, 2)
arr.distinct().also {
    
    
    println(it) //[1, 2]
}
val arr = intArrayOf(1, 2, 3)
arr.distinctBy {
    
    
    it % 2
}.also {
    
    
    println(it) //[1, 2]
}

AnyUnitNothing

Kotlin Java
Any Object
Unit Void
Nothing 没有对应的类型,编译后为Void

Any是所有类型的父类,Unit是表达式没有返回值时用来替代的一种类型。

Nothing

不可以实例化,是所有类型的子类,常用在抛出异常的方法,作为返回值类型(只抛出异常,没有什么可以返回)。

fun main() {
    
    
    var str = "123"
    var len = size(str)
    println(len)
}

fun size(str: String?): Int {
    
    
    return str?.length ?: error("不能为null")
}

//不加Nothing,size方法会报错,error方法返回的是Unit类型,与size方法返回类型不兼容
//加Nothing,编译器会检测出异常出现的位置,判断出死代码的范围并提醒
//Nothing是所有类型的子类,与size方法的Int返回类型兼容
fun error(msg: String): Nothing {
    
    
    throw RuntimeException(msg)
}

集合

包路径:kotlin.collections.*

不可变集合

只能查询集合中的元素,不能修改。

优点:

  • 被不可信任的库调用时,不可变集合更安全。
  • 在多线程环境下,没有并发的安全性问题。
  • 不可变集合节省内存空间,不需要扩容
val list: List<Int> = listOf(1, 2, 3)
println(list.javaClass)//java.util.Arrays$ArrayList
println(list)//[1, 2, 3]

val set: Set<Int> = setOf(1, 2, 3)
println(set.javaClass)//java.util.LinkedHashSet
println(set)//[1, 2, 3]

val map: Map<Int, String> = mapOf(
	1 to "01", 2 to "02", 3 to "03"
)
println(map.javaClass)//java.util.LinkedHashMap
println(map)//{1=01, 2=02, 3=03}

可变集合

可以对集合中的元素进行增删改查。

val list: List<Int> = mutableListOf(1, 2, 3)
println(list.javaClass)//java.util.ArrayList
println(list)//[1, 2, 3]

val set: Set<Int> = mutableSetOf(1, 2, 3)
println(set.javaClass)//java.util.LinkedHashSet
println(set)//[1, 2, 3]

val map: Map<Int, String> = mutableMapOf(
	1 to "01", 2 to "02", 3 to "03"
)
println(map.javaClass)//java.util.LinkedHashMap
println(map)//{1=01, 2=02, 3=03}
val list: List<Int> = arrayListOf(1, 2, 3)
println(list.javaClass)//java.util.ArrayList
println(list)//[1, 2, 3]

val set: Set<Int> = hashSetOf(1, 2, 3)
println(set.javaClass)//java.util.HashSet
println(set)//[1, 2, 3]

val map: Map<Int, String> = hashMapOf(
	1 to "01", 2 to "02", 3 to "03"
)
println(map.javaClass)//java.util.HashMap
println(map)//{1=01, 2=02, 3=03}
val set: Set<Int> = linkedSetOf(1, 2, 3)
println(set.javaClass)//java.util.LinkedHashSet
println(set)//[1, 2, 3]

val map: Map<Int, String> = linkedMapOf(
	1 to "01", 2 to "02", 3 to "03"
)
println(map.javaClass)//java.util.LinkedHashMap
println(map)//{1=01, 2=02, 3=03}
val set: Set<Int> = sortedSetOf(1, 2, 3)
println(set.javaClass)//java.util.TreeSet
println(set)//[1, 2, 3]

val map: Map<Int, String> = sortedMapOf(
	1 to "01", 2 to "02", 3 to "03"
)
println(map.javaClass)//java.util.TreeHashMap
println(map)//{1=01, 2=02, 3=03}

常用方法

val list: List<Int> = listOf(1, 2, 3)
//有一个不满足,返回false
list.all {
    
     n -> n == 3 }
	.apply {
    
     println(this) }//false
//有一个满足条件,返回true
list.any {
    
     n -> n != 3 }
	.apply {
    
     println(this) }//true
//统计满足条件的元素个数
list.count {
    
     it >= 2 }
	.apply {
    
     println(this) }//2
//寻找第一个满足条件的元素
list.find {
    
     n -> n >= 2 }
	.apply {
    
     println(this) }//2
list.firstOrNull {
    
     n -> n >= 2 }
	.apply {
    
     println(this) }//2
//按条件分组
list.groupBy {
    
     n -> n % 2 }
	.apply {
    
     println(this) }//{1=[1, 3], 0=[2]}
//过滤不符合条件的元素
list.filter {
    
     n -> n % 2 == 0 }
	.apply {
    
     println(this) }//[2]
//寻找最大值
listOf("asx", "s", "qza", "amount")
	.maxByOrNull(String::length)
	.apply {
    
     println(this) }//amount

序列

集合调用filter方法和map等处理元素的方法时,会创建中间临时集合对象进行封装。为了避免这个过程,引入sequence序列。

var list: List<Int> = listOf(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
list = list.asSequence()
    //[5,6,7,8,9,a],不执行
    .filter {
    
     n -> n > 4 }
    //[a,c,e,10,12,14],不执行
    .map {
    
     n -> n * 2 }
    //归集方法
    .toList()
    //开始执行序列设计好的上述过程
    //每个元素都会经过上述操作,避免中间创建临时对象
println(
    //序列格式化输出
    list.joinToString(
        "[", //左边界
        ",", //分隔符
        "]", //右边界
        10, //最多显示的元素个数
        "...", //超出限制数量不显示的元素,使用填充
        {
    
     it.toString(16) } //转换元素
    )
)

字符串

比较

fun main() {
    
    
    //创建
    val str = "abcDefgHi"
    val str1 = "$str" + " " + "${
      
      str.length}"
    val str2 = buildString {
    
    
        append("abcdefghi").append(' ').append(str.length)
    }
    
    //内容相同(引用可能不同),false
    (str1 == str2).ptl()
    //引用相同,false
    (str1 === str2).ptl()
    //忽略大小写比较,true
    (str1.equals(str2, true)).ptl()
    //匹配前缀,true
    (str1.startsWith("AB", 0, true)).ptl()
    //匹配后缀,false
    (str1.endsWith("hi", true)).ptl()
}

//扩展方法:为特定类型添加方法(并未修改源文件)
//作用域受这个方法的修饰词控制
fun Any?.ptl() {
    
    
    println(this)
}

处理

fun main() {
    
    
    //获取第一个字符
    "abcDefgHi"[0].ptl() //a
    //长度为0的字符串=>NoSuchElementException
    "abcDefgHi".first().ptl() //a
    //长度为0的字符串=>null
    "abcDefgHi".firstOrNull().ptl() //a
    
    //获取最后一个字符
    "abcDefgHi".last().ptl() //i
    "abcDefgHi".lastOrNull().ptl() //i
    "abcDefgHi".lastOrNull {
    
     it == 'c' }.ptl() //c
    
    //截取
    "abcDefgHi".drop(3).ptl() //DefgHi
    "abcDefgHi".dropLast(3).ptl() //abcDef
    
    //删除前后特定字符
    "_1_234_567_".removeSurrounding("_").ptl() //1_234_567
    
    //分组
    "1,23_4,567".split("_", ",").ptl() //[1, 23, 4, 567]
    
    //设置默认值
    " \n \r \t".ifBlank {
    
     "空字符串" }.ptl()
    "".ifEmpty {
    
     "0长度字符串" }.ptl()
    
    //资源路径截取
    val path = "C:\\Windows\\System32\\cmd.exe"
    //文件名
    path.substring(path.lastIndexOf('\\') + 1).ptl()
    path.removeRange(0..path.lastIndexOf('\\')).ptl()
    path.substringAfterLast('\\').ptl()//推荐
    //批量去除后缀名
    listOf("1.jpg", "2.jpg", "3.jpg").map {
    
     it.removeSuffix(".jpg") }.forEach(::println)
    listOf("1.jpg", "2.png", "3.tiff").map {
    
     it.substringBefore(".") }.forEach(::println)
    
    //首字母大写
    "system".capitalize().ptl()
    "system".replaceFirstChar {
    
    
        if (it.isLowerCase()) {
    
    
            it.titlecase(Locale.getDefault())
        } else {
    
    
            it.toString()
        }
    }.ptl()
}

fun Any?.ptl() {
    
    
    println(this)
}

原始字符串

fun main() {
    
    
    //转义符失效
    """
    asdf \t \n \r 123
    """.ptl()
    //保留每一行和前面的空格
    
    """
    asdf \t \n \r 123
    """.trimIndent().ptl()
    //删除前后的空行和左边的空格
    
    """
    |public static void main(String[] args) {
    |System.out.println("Hello World!");
    |}
    """.trimMargin("|").ptl()
    //默认以”|“字符为左边界,删除左边的字符和多余的空行
    
    //支持拼接
    ("""123""" + """sada""" + "125").ptl()
}

fun Any?.ptl() {
    
    
    println(this)
}

泛型

型变、约束

open class Animal //动物
open class Dog : Animal() //狗
open class Shiba : Dog()//柴犬

open class Shop<T> {
    
    
    fun run(t: T?): T? = t
}

fun main() {
    
    
    //out:协变,<? extends Dog>,子类泛型可以赋值给父类泛型
    val s1: Shop<out Dog> = Shop<Animal>() //error
    val s2: Shop<out Dog> = Shop<Dog>()
    val s3: Shop<out Dog> = Shop<Shiba>()
    val s2run: Dog? = s2.run(/*Nothing*/null)//能获取,不能传入
    //in:逆变,<? super Dog>,父类泛型可以赋值给子类泛型
    val s4: Shop<in Dog> = Shop<Animal>()
    val s5: Shop<in Dog> = Shop<Dog>()
    val s6: Shop<in Dog> = Shop<Shiba>() //error
    val s5run: Any? = s5.run(Dog())//能传入,不能获取
    //不变,<Dog>,类型被限定
    val s7: Shop<Dog> = Shop<Animal>() //error
    val s8: Shop<Dog> = Shop<Dog>()
    val s9: Shop<Dog> = Shop<Shiba>() //error
    val s8run: Dog? = s8.run(Dog())//能获取,能传入
    //投影,<in Nothing>,<out Any>,类型不被限定
    val s10: Shop<*> = Shop<Animal>()
    val s11: Shop<*> = Shop<Dog>()
    val s12: Shop<*> = Shop<Shiba>()
    val s11run: Dog? = s8.run(Dog())//能获取,能传入
}

方法

可选参数

import kotlin.math.pow

//参数拥有默认值后,变为可选参数,不需要赋值也可以使用
fun pow(
    a: Double = .0,
    base: Double = 10.0,
    c: Double = 1.0
) = a + base.pow(c) //表达式方法

fun main() {
    
    
//跨参数赋值可以使用参数名指定
    println(pow(1.0, c = 3.0)) //1001.0
}

vararg

参数的数量可以不固定,使用vararg修饰的参数为可变长度的参数,本质是一个数组。

位置不受限制,放在固定参数的前边是数组,放在后边是Java的T...

fun add(vararg arr: Int, last: Int): IntArray {
    
    
    val temp = arr.copyOf(arr.size + 1)
    temp[arr.size] = last
    return temp
}

fun main() {
    
    
    //println(add(last = 0))//ERROR

    //[0]
    println(add(last = 0).contentToString())

    //[1, 2, 0]
    println(add(1, 2, last = 0).contentToString())

    //[1, 2, 3, 4, 0]
    println(add(1, 2, 3, 4, last = 0).contentToString())
}

*

展开参数的操作符。避免将int[]传入T...时,int[]被当作一个元素的T

fun main() {
    
    
    val arr = arrayOf(1, 2, 3)
    //识别为一个对象
    //public fun <T> listOf(element: T): List<T>
    listOf(arr).run {
    
     println("$size") } //1
    //数组也是一个对象,使用*后,匹配vararg形参
    //public fun <T> listOf(vararg elements: T): List<T>
    listOf(*arr).run {
    
     println("$size") } //3
}

infix

两个相同类型的形参方法,使用infix修饰方法,可以放在两个相同的参数之间,不需要使用点和括号的方法,构成中缀表达式的语法。

infix fun Int.max(that: Int) = this.coerceAtLeast(that)

fun main() {
    
    
    //相当于 5.max(6)
    println(5 max 6)//6
}

for循环的untildownTostep使用了中缀表达式。

Map的键值对使用key to value的方式生成Pair对象。

扩展方法

在方法名前使用类型名进行指定扩展的方法。并没有在指定类型的源文件中进行扩展。

//为所有类型扩展ptl方法
//在哪里扩展,从哪里来调用
fun Any?.ptl() {
    
    
    println(this)
}

局部方法

方法里面嵌套的方法。

fun main() {
    
    
    fun max(a: Int, b: Int) = a.coerceAtLeast(b)
    max(5, 6).ptl()//6
}
fun Any?.ptl() = println(this)

匿名方法

fun main() {
    
    
    (fun(a: Int, b: Int): Int {
    
    
        return a.coerceAtLeast(b)
    })(5, 6).ptl()//6
}
fun Any?.ptl() = println(this)

进阶篇

构造方法

创建对象时用于初始化类属性的方法,返回创建的对象。

在类名后为一级构造方法,在类内为二级构造方法。

//一级构造方法的参数如果没有被var、val标记
//不会生成属性、get、set方法
//constructor可以省略
open class Person constructor(var name: String) {
    
    
    var age: Int = 0

    //二级构造方法的参数不可以被var、val标记
    constructor(name: String, age: Int) : this(name) {
    
    
        this.age = age
    }
}

//顶级成员类的构造方法私有化
//object 类名
object Man

伴生对象

伴生对象:静态类成员。每个类只能有一个伴生对象

open class Math {
    
    
    //companion object name
	//name缺省时,默认名为:Companion
    companion object Const{
    
    
        //公共常量
        const val INT_MAX: Int = Int.MAX_VALUE
        //私有常量
        val INT_MIN: Int = Int.MIN_VALUE
        //静态变量
        var INT_BITS: Int = Int.SIZE_BITS
        //静态方法
        fun max(x: Int, y: Int): Int {
    
    
            return x.coerceAtLeast(y)
        }
    }
}

fun main() {
    
    
    Math.INT_MAX //Kotlin直接调用
    Math.Const.INT_MAX //兼容Java的调用
}

单例模式

(主要是了解构造方法怎么私有化。)

饿汉

//将构造方法私有化
object Person

fun main() {
    
    
    val p1 = Person
    val p2 = Person
    println(p1 === p2)//true
}

懒汉

//将构造方法私有化
class Person private constructor() {
    
    
    companion object {
    
    
        private var INSTANCE: Person? = null
        fun getInstance(): Person {
    
    
            if (INSTANCE === null) {
    
    
                //创建并保存单例对象
                INSTANCE = Person()
            }
            return INSTANCE!!
        }
    }
}

fun main() {
    
    
    val p1 = Person.getInstance()
    val p2 = Person.getInstance()
    println(p1 === p2)
}

线程安全的懒汉

class Person private constructor() {
    
    
    companion object {
    
    
        private var INSTANCE: Person? = null
        @Synchronized //这个方法将被加上同步锁关键字
        fun getInstance(): Person {
    
    
            if (INSTANCE === null) {
    
    
                INSTANCE = Person()
            }
            return INSTANCE!!
        }
    }
}

fun main() {
    
    
    val p1 = Person.getInstance()
    val p2 = Person.getInstance()
    println(p1 === p2)
}

双重校验锁

class Person private constructor() {
    
    
    companion object {
    
    
        private val INSTANCE: Person by lazy(
            //懒加载的同步锁
            mode = LazyThreadSafetyMode.SYNCHRONIZED
        ) {
    
    
            return@lazy Person()
        }
        fun getInstance(): Person {
    
    
            return INSTANCE
        }
    }
}

fun main() {
    
    
    val p1 = Person.getInstance()
    val p2 = Person.getInstance()
    println(p1 === p2)
}

静态内部类模式

class Person private constructor() {
    
    
    companion object {
    
    
        private object PersonSingleton {
    
    
            val singleton = Person()
        }

        private val INSTANCE: Person = PersonSingleton.singleton
        fun getInstance(): Person {
    
    
            return INSTANCE
        }
    }
}

fun main() {
    
    
    val p1 = Person.getInstance()
    val p2 = Person.getInstance()
    println(p1 === p2)
}

匿名内部类对象

class Person {
    
    
    fun main() {
    
    
        //抽象类不能创建对象,但是实现全部方法后
        //就可以创建内部类的匿名对象
        //然后直接调用对象的方法
        object : AbstractCollection<Int>() {
    
    
            override val size: Int
                get() = TODO()

            override fun iterator(): Iterator<Int> {
    
    
                TODO()
            }
        }.stream().forEach(::println)
    }
}

init

初始化代码块。创建对象,初始化类时,最先被执行的代码。

class Person {
    
    
    //会添加到一级构造方法中,每次创建对象时最先执行
    //如果没有一级构造方法,则添加到每一个二级构造方法中
    //如果在companion中,自动转换为static{ }
    init {
    
    
        TODO()
    }
}

inner

创建的内部类默认为静态内部类,如果需要去掉静态,使用关键字inner

open class Person {
    
    
    //public static final
    class Man {
    
    
    }

    //public final
    inner class Woman {
    
    
    }
}

sealed

密封类:枚举的扩展。被标记后是抽象类。

sealed class Person {
    
    
    class Man() : Person()
    class Woman() : Person()
}

fun func(p: Person) {
    
    
    when (p) {
    
    
        is Person.Man -> println("男")
        is Person.Woman -> println("女")
        else -> println("中性") //多余的分支
    }
}

data class

数据类:自动为一级构造方法的属性创建一些特殊的方法。只对一级构造方法的参数有用。

//equals() / hashCode()
//toString() 格式如 "Person(name=John, age=42)"
//componentN() functions 对应于属性,按声明顺序排列
//copy() 函数
data class Person(var id: Int)

interface

接口类。

interface IPerson {
    
    
    //接口的属性不允许赋值,实现时必须重写,get和set方法为抽象方法
    var id: Int

    //如果没有方法体,为抽象方法
    //如果有方法体,除了抽象方法,还将实现封装在DefaultImpls类中,成为默认方法
    fun idPlus() {
    
    
        id++
    }
}

class Person : IPerson {
    
    
    //override 重写接口中的成员
    override var id: Int = 0
    override fun idPlus() {
    
    
        //调用默认方法
        super.idPlus()
        println(id)
    }
}

类的成员

类由多个字段和方法构成。

字段、属性

属性的声明由valvar标记,一级构造方法也是如此。

abstract修饰的抽象成员可以不用初始化赋值。

  • private标记,没有setget方法
  • val标记,为属性,只有get方法
  • var标记,为属性,setget方法都有
class Person {
    
    
    //Field 类的字段。
    private val id = 0 //字段

    //Property 字段和对应的get、set方法组成类的属性。
    val age = 0 //age属性
    var name = "" //name属性
}

fun main() {
    
    
    val p = Person()
    val fields = p.javaClass.declaredFields
    for (field in fields) {
    
    
        println(field.name)
    }
}

field

手动为属性添加getset方法

class Person {
    
    
    val age: Int = 18
        //field指代这个属性的值,使用this.age会出现无限的嵌套调用
        get() = field
    var name: String = "无名氏"
        //get方法不可以私有化,属性必须可读
        get() {
    
    
            return field
        }
        //set方法可以私有化,只供内部调用
        private set(name) {
    
    
            if (name.isNotEmpty()) field = name
        }
}

const

常量,只能修饰原始类型和字符串。

class Person {
    
    
    //常量必须放在伴生对象中
    companion object {
    
    
        //public static final
        const val MAN = 1
        const val WOMAN = 2
    }
}

lateinit

懒加载的属性不可以为原始数据类型(不能为null)

class Person {
    
    
    //属性只能为使用var修饰,可以不用初始化
    lateinit var name: String
    override fun toString(): String {
    
    
        if (this::name.isInitialized.not()) this.name = ""
        //如果调用没有初始化的属性,将抛出异常
        //UninitializedPropertyAccessException
        return "Person(name='$name')"
    }
}

fun main() {
    
    
    val p = Person()
    //Person(name='')
    println(p.toString())
    p.name = "张三"
    //Person(name='张三')
    println(p.toString())
}

委托

by

属性的get和set方法实现,可以委托给别的类的对象进行重载。

import kotlin.reflect.KProperty

class Name {
    
    
    //使用委托对象后,这个委托对象没有name字段,只有name字段的get和set方法的重载
    //operator 重载
    operator fun getValue(p: Person, pro: KProperty<*>): String {
    
    
        println("getValue: 将 ${
      
      pro.name} 委托到 ${
      
      this.toString()} 来处理")
        return pro.name
    }

    operator fun setValue(p: Person, pro: KProperty<*>, s: String) {
    
    
        println("setValue: 将 $s 委托到 ${
      
      this.toString()} 来处理,然后交给 ${
      
      pro.name}")
    }
}

class Person {
    
    
    //委托Name对象完成get和set方法的实现
    var name: String by Name()
    override fun toString(): String {
    
    
        return "Person(name='$name')"
    }
}

fun main() {
    
    
    val p = Person()
    p.name = "123"
    println(p.toString())
}

by lazy

import kotlin.concurrent.thread

class Person {
    
    
    //延迟加载的属性只能使用val修饰
    private val name: String by lazy(LazyThreadSafetyMode.SYNCHRONIZED) {
    
    
        println("${
      
      Thread.currentThread().id.toString()} lazy")
        "123"
    }

    override fun toString(): String {
    
    
        Thread.sleep(10)
        return "${
      
      Thread.currentThread().id.toString()} Person(name='$name')"
    }
}

fun main(args: Array<String>) {
    
    
    val p = Person()
    repeat(10) {
    
    
        thread(name = "线程:${
      
      it}") {
    
    
            println(p.toString())
        }
    }
}

LazyThreadSafetyMode

  • SYNCHRONIZED:默认,多线程环境下,只会执行一次,然后任何线程就不会再执行
  • PUBLICATION:多个线程都可能会执行一次,直到检测到已经初始化后不再执行
  • NONE:没有线程安全的保障,只能在单线程中安全的使用

by Delegates.observable

属性的值发生变化时的监听处理。

import kotlin.properties.Delegates

class Person {
    
    
    //设置初始值<init>
    var name: String by Delegates.observable("<init>") {
    
     pro, old, new ->
        //然后每次变化前会执行这个方法
        println("属性名:${
      
      pro.name} , ${
      
      old} -> ${
      
      new}")
    }

    override fun toString(): String {
    
    
        return "Person(name='$name')"
    }
}

fun main() {
    
    
    val p = Person()
    p.name = "A"
    p.name = "B"
    println(p.toString())
}

by map

使用Map对象初始化对象的属性值。键是属性名,值是属性值。

class Person(val map: Map<String, Any>) {
    
    
    private val id: Int by map
    private val name: String by map
    override fun toString(): String {
    
    
        return "Person(id=$id, name='$name')"
    }
}

fun main() {
    
    
    val p = Person(hashMapOf("id" to 1, "name" to "a"))
    println(p.toString())
}

委托模式

当两个子类(继承同一个类)的实现完全一致,可以只实现一个子类,另一个子类交给这个类去实现。一处修改,另一个自动修改。

interface People {
    
    
    fun talk()
}

class Man : People {
    
    
    override fun talk() {
    
    
        println("talk")
    }
}

//Woman使用代理对象man实现接口,实现和Man类完全一致
class Woman(private val man: Man = Man()) : People by man

fun main() {
    
    
    val w = Woman()
    w.talk()
}

枚举

可以约束传参时的值在合理的范围区间。

enum class Color(var color: Int) {
    
    
    RED(0xFF0000),
    WHITE(0xFFFFFF),
    BLACK(0x000000),
    GREEN(0x00FF00)
}

fun toString(color: Color): String {
    
    
    //when判断枚举
    return when (color) {
    
    
        Color.RED -> "红"
        Color.BLACK -> "黑"
        Color.GREEN -> "绿"
        Color.WHITE -> "白"
    }
}

fun main() {
    
    
    println(toString(Color.WHITE))
}

密封类和枚举对比

密封类比枚举类更灵活。常用来表示受限制的类继承结构(密封类中的内部类都必须继承这个密封类)。

枚举中,每个枚举值都是枚举的单例,密封类中的子类可以不是单例(更自由灵活)。

密封类的子类都必须在这个密封类中,但是密封类子类的子类不受限制。

密封类是抽象类,不能实例化,内部的子类可以实例化。

常用方法

import java.io.Serializable

//枚举可以实现接口,不能继承类
enum class Country : Serializable {
    
    
    USA {
    
    
        //匿名内部类,重载方法
        override fun toString(): String = "美国"
    },
    KR,
    JP;
}

fun main() {
    
    
    //名称
    Country.USA.ptl()//美国
    Country.USA.name.ptl()//USA
    //序号,when索引时底层使用
    Country.JP.ordinal.ptl()//2
    //使用name获取枚举值
    //不存在抛出异常java.lang.IllegalArgumentException
    Country.valueOf("USA").ordinal.ptl()//0
    //输出全部枚举值
    Country.values().toList().ptl()//[美国, KR, JP]
}

fun Any?.ptl() = println(this)

实例域

使用枚举时不推荐使用ordinalname(序列化和反序列化出现差异时无法避免null),不推荐作为接口的返回类型。

//使用实例域代替ordinal序数,避免枚举发生变化时,无法对应
enum class Country(val id: Int) {
    
    
    USA(101),
    KR(201),
    JP(202);
}

fun main() {
    
    
    Country.USA.id.ptl()
}

fun Any?.ptl() = println(this)

操作符重载

https://github.com/JetBrains/kotlin/blob/1.8.0/spec-docs/operator-conventions.md

import kotlin.math.abs
import kotlin.math.max
import kotlin.math.min
//分数类
class Fraction private constructor() {
    
    
    private var p: Int = 0 //分子
    private var q: Int = 0 //分母

    constructor(p: Int, q: Int) : this() {
    
    
        if (q == 0) throw Exception("q != 0")
        if (q < 0) {
    
    
            this.q = -q
            this.p = -p
        }
        val g = gcd(abs(p), q)
        this.p = p / g
        this.q = q / g
    }

    //this + a
    operator fun plus(a: Fraction): Fraction {
    
    
        return Fraction(a.p * this.q + a.q * this.p, a.q * this.q)
    }

    //this += a
    operator fun plusAssign(a: Fraction) {
    
    
        val f = this + a
        this.p = f.p
        this.q = f.q
    }

    //-this
    operator fun unaryMinus(): Fraction {
    
    
        return Fraction(-this.p, this.q)
    }

    //this - a
    operator fun minus(a: Fraction): Fraction {
    
    
        return this + -a
    }

    //this * a
    operator fun times(a: Fraction): Fraction {
    
    
        return Fraction(this.p * a.p, this.q * a.q)
    }

    //this / a
    operator fun div(a: Fraction): Fraction {
    
    
        return Fraction(this.p * a.q, this.q * a.p)
    }

    //this % a
    //operator fun rem(other: Fraction): Fraction
    companion object {
    
    
        fun gcd(a: Int, b: Int): Int {
    
    
            if (a == 0 || b == 0) return 0
            var t: Int
            var max: Int = max(a, b)
            var min: Int = min(a, b)
            while (max % min != 0) {
    
    
                t = max % min
                max = min
                min = t
            }
            return min
        }
    }

    private fun toDouble(): Double {
    
    
        return p.toDouble() / q
    }

    override fun toString(): String {
    
    
        return toDouble().toString()
    }
}

fun main() {
    
    
    val f_2_3 = Fraction(2, 3)
    val f_3_7 = Fraction(3, 7)
    println(f_2_3 - f_3_7) //0.23809523809523808
    println(f_2_3 * f_3_7) //0.2857142857142857
    println(f_2_3 / f_3_7) //1.5555555555555556
    f_2_3 += f_3_7
    println(f_2_3) //1.0952380952380953
}

一元操作符

操作符 函数名 接口方法
+ a unaryPlus operator fun unaryPlus(): T
- a unaryMinus operator fun unaryMinus(): T
! a not operator fun not(): Boolean
++a inc operator fun inc(): T
a++ inc operator fun inc(): T
--a dec operator fun dec(): T
a-- dec operator fun dec(): T

基本运算符

操作符 函数名 接口方法示例
a + b plus operator fun plus(t: T): T
a - b minus operator fun minus(t: T): T
a * b times operator fun times(t: T): T
a / b div operator fun div(t: T): T
a % b rem operator fun rem(t: T): T

复合赋值操作运算符

操作符 函数名 接口方法示例
a += b plusAssign operator fun plusAssign(t: T)
a -= b minusAssign operator fun minusAssign(t: T)
a *= b timesAssign operator fun timesAssign(t: T)
a /= b divAssign operator fun divAssign(t: T)
a %= b remAssign operator fun remAssign(t: T)

如果只重载了plus运算符,执行a+=b时,自动展开为a=a+b,然后调用plus方法。

如果同时重载了plusAssignplus运算符,执行a+=b时,直接调用plusAssign方法。

按位操作符

Kotlin没有Java的按位操作符,通过中缀方法实现。

操作符 函数名 接口方法示例
a & b a and b infix fun and(t: T): T
a | b a or b infix fun or(t: T): T
~ a a inv b operator fun inv(): T
a ^ b a xor b infix fun xor(t: T): T
a << b a shl b infix fun shl(t: T): T
a >> b a shr b infix fun shr(t: T): T
a >>> b a ushr b infix fun ushr(t: T): T

比较操作符

操作符 函数名 接口方法示例
a == b equals override fun equals(other: Any?): Boolean
a != b !(equals) override fun equals(other: Any?): Boolean
a < b compareTo < 0 operator fun compareTo(other: BigUInt): Int
a <= b compareTo <= 0 operator fun compareTo(t: T): Int
a > b compareTo > 0 operator fun compareTo(t: T): Int
a >= b compareTo >= 0 operator fun compareTo(t: T): Int

===!==运算符不支持扩展函数的操作符重载。

索引操作符

操作符 函数名 接口方法示例
v = a[b] get(b) operator fun get(n: Int): T
v = a[x, y] get(x, y) operator fun get(x: Int, y: Int): T
a[b] = v set(b, v) operator fun set(n: Int, v: T)
a[x, y] = v set(x, y, v) operator fun set(x: Int, y: Int, v: T)

可以为多个参数。

调用操作符

操作符 函数名 接口方法示例
a(b) a.invoke(b) operator fun invoke(b: Int)
a(x, y) a.invoke(x, y) operator fun invoke(x: Int, y: Int)

可以为多个参数。

in操作符

判断存在于某个范围内。遍历数组或集合。

操作符 函数名 接口方法示例
a in b b.contains(a) operator fun contains(t: T): Boolean
a !in b !b.contains(a) operator fun contains(t: T): Boolean
for(a in b) b.iterator(){a} operator fun iterator(): Iterator<T>

区间操作符

操作符 函数名 接口方法示例
a..b a.rangeTo(b) operator fun rangeTo(t: T): ClosedRange<out Comparable<T>>

解构操作符

componentN

class Fraction(private var a: Int, private var b: Int) {
    
    
    operator fun component1(): Int {
    
    
        return this.a
    }
    operator fun component2(): Int {
    
    
        return this.b
    }
}

fun main() {
    
    
    val f = Fraction(1, 3)
    //a=>component1,b=>component2
    val (a, b) = f
}
  • 两个返回值元组类:kotlin.Pair
  • 三个返回值元组类:kotlin.Triple

`(反引号)

反引号可以解决关键字冲突的问题,强行将一个不合法的字符变为合法。

fun main(args: Array<String>) {
    
    
    val r = 2 `**` 3
    `This is a println function`(r)
}

//自定义运算符
infix fun Number.`**`(b: Number): Double {
    
    
    return Math.pow(this.toDouble(), b.toDouble())
}

//自定义方法名,空格不受命名规则限制
fun `This is a println function`(text: Any) {
    
    
    println(text)
}

文件

读写

import java.io.File
import kotlin.random.Random

fun main() {
    
    
    //文件不存在会自动创建
    val write = File("""D:\1.txt""")
    if (write.exists().not()) write.createNewFile()
    //覆盖
    write.writeBytes("123".toByteArray(Charsets.UTF_8))
    write.writeText("123") //默认使用UTF-8编码
    //追加
    write.appendBytes("12".toByteArray())
    write.appendText("12")
    //使用Java的IO流,完全覆盖
    write.outputStream().use {
    
    
        val sb = StringBuilder()
        for (i in 0..10) {
    
    
            sb.append(Random.nextInt(0x4E00, 0x9FA5).toChar())
        }
        //默认使用UTF-8编码
        it.write(sb.toString().toByteArray())
    }

    //文件不存在,抛出FileNotFoundException
    val read = File("""D:\1.txt""")
    //按行读取
    ptl {
    
     sb ->
        read.forEachLine {
    
     sb.append(it) }
    }
    //按数组块大小读取
    ptl {
    
     sb ->
        read.forEachBlock {
    
     buffer: ByteArray, n: Int ->
            //默认的块大小是4096,块大小不能小于512
            sb.append(buffer.decodeToString(0, n))
        }
    }
    //按字节读取
    ptl {
    
     sb ->
        read.inputStream().use {
    
    
            var value: Int = it.read()
            while (value != -1) {
    
    
                sb.append(value.toString(16)).append(" ")
                value = it.read()
            }
        }
    }
    //使用缓冲区
    ptl {
    
     sb ->
        read.inputStream().use {
    
    
            val buffer = ByteArray(1024)
            var n: Int
            do {
    
    
                n = it.read(buffer, 0, buffer.size)
                if (n == -1) break
                sb.append(buffer.decodeToString(0, n))
            } while (true)
        }
    }

    //使用缓冲流
    ptl {
    
     sb ->
        read.bufferedReader().use {
    
    
            it.readLine().forEach {
    
     sb.append(it.toString()) }
        }
    }
}

//高阶函数,内联函数
inline fun ptl(f: (sb: StringBuilder) -> Unit) {
    
    
    val str = StringBuilder()
    f(str)
    println(str)
}

遍历

import java.io.File

fun main() {
    
    
    val file: File = File("C:\\Windows\\System32\\cmd.exe")
    println(file.name) //cmd.exe
    println(file.nameWithoutExtension) //cmd
    println(file.extension) //exe

    File("C:\\")
        //父目录
        //.parentFile
        //访问
        .walk()
        //遍历深度
        .maxDepth(Int.MAX_VALUE)
        //过滤后缀
        .filter {
    
     it.extension.equals("log", true) }
        .forEach {
    
    
            println(it.absolutePath)
            it.delete()
        }
}

拷贝

import java.io.File

fun main() {
    
    
    val file = File("""D:\1.txt""")
    //文件拷贝
    //当前目录复制粘贴
    file.copyTo(File(file.parent, "3.txt"), overwrite = true)
    //目录拷贝
    file.parentFile?.copyRecursively(
        //文件所在的目录拷贝到另一个目录
        File(file.parentFile.parent, "2"), overwrite = true
    )
}

异常

kotlin的异常捕获由程序员来评估,自行捕获或规避。

如果通过编译器提醒进行捕获,滥用检查性异常会导致代码冗余,过多不想处理的异常不断向上抛出。

import java.io.File

class EmptyFileException(message: String?) : Exception(message)

fun main() {
    
    
    //不需要捕获异常,所有的异常都是unchecked exception
    val file = File("C:\\Windows\\System32\\cmd.exe")
    println(file.absolutePath)
    //根据需要抛出异常
    if (file.length() == 0L) throw EmptyFileException("没有内容")

    val str = ""
    try {
    
    
        str.toInt() //制造一个异常
    } catch (e: NumberFormatException) {
    
    
        println("$e $str is empty")
    }
}

高级篇

注解

元注解

注解类中用来描述注解类特性的注解。

//Flag注解一直存活到编译后
@Retention(AnnotationRetention.RUNTIME)
//可以在同一个位置重复标记
@Repeatable
//Flag重复标记时,会存放在Flag$Container注解容器
//如果需要指定注解容器,Java调用时兼容
@JvmRepeatable(Flags::class)
//被标记的成员将生成API文档
@MustBeDocumented
annotation class Flag

//Flag注解被Target限制在只能在字段和属性上标记
@Target(AnnotationTarget.FIELD, AnnotationTarget.ANNOTATION_CLASS)
annotation class Flags(val value: Array<Flag>) //必须使用val
AnnotationTarget 描述
CLASS
ANNOTATION_CLASS 注解类
TYPE_PARAMETER 泛型形参
PROPERTY 属性
FIELD 字段、枚举常量
LOCAL_VARIABLE 局部变量
VALUE_PARAMETER 方法形参
CONSTRUCTOR 构造方法
FUNCTION 方法
PROPERTY_GETTER 属性的获取方法
PROPERTY_SETTER 属性的设置方法
TYPE 类型
EXPRESSION 表达式
FILE 源文件
TYPEALIAS 类型别名
AnnotationRetention 描述
SOURCE 源码可见,编译后无法获取
BINARY 编译后可见,运行时无法获取
RUNTIME 运行时可以获取

位置注解

更精确的添加注解。

@file:Flag//将Flag注解添加到源码

@Target(
    AnnotationTarget.FILE,
    AnnotationTarget.VALUE_PARAMETER,
    AnnotationTarget.LOCAL_VARIABLE,
    AnnotationTarget.PROPERTY,
    AnnotationTarget.PROPERTY_GETTER,
    AnnotationTarget.PROPERTY_SETTER,
    AnnotationTarget.FIELD,
    AnnotationTarget.FUNCTION
)
annotation class Flag

class Person {
    
    

    //根据Target注解顺序指定
    //优先级:参数->属性->字段
    //get添加到getId$annotations
    @Flag
    var id: Int = 0

    @field:Flag //字段上添加Flag注解
    @property:Flag//属性上添加Flag注解
    @get:Flag //getXXX方法上添加Flag注解
    @set:Flag //setXXX方法的参数上添加Flag注解
    @setparam:Flag//setXXX方法上添加Flag注解
    var name: String = ""

    @delegate:Flag//属性委托的XXX$delegate字段上添加Flag注解
    val age: Int by lazy {
    
     18 }
}

//在被扩展的实例上(第一个参数)添加Flag注解
fun @receiver:Flag Person?.toJson(): String {
    
    
    return "{id=$this.id, name=${
      
      this?.name}, age=${
      
      this?.age}}"
}

Java注解

注解名 作用
@Volatile Java关键字volatile
@Strictfp Java关键字strictfp
@JvmName 改变由kotlin生成的java方法或字段的名称
@JvmStatic 用在对象声明或者伴生对象的方法上,把它们暴露成java的静态方法
@JvmOverload 指导kotlin编译器为带默认参数值的函数生成多个重载函数
@JvmField 将属性暴露成一个没有访问修饰符的公有java字段
@Synchronized 为被标记的方法添加线程同步锁

反射

需要引入kotlin-reflect依赖。

ORM框架

import kotlin.reflect.full.declaredMemberProperties
import kotlin.reflect.full.findAnnotation

@Target(AnnotationTarget.CLASS)
@Retention(AnnotationRetention.RUNTIME)
annotation class Table(val name: String = "")

@Target(AnnotationTarget.PROPERTY)
@Retention(AnnotationRetention.RUNTIME)
annotation class ID(val name: String = "")

@Target(AnnotationTarget.PROPERTY)
@Retention(AnnotationRetention.RUNTIME)
annotation class Field(val name: String = "")

interface IDao<T> {
    
    
    fun add(): Boolean
    fun delete(): Boolean
    fun update(): Boolean
    fun select(): Boolean
}

abstract class Dao<T> : IDao<T> {
    
    
    override fun add(): Boolean {
    
    
        val kc = javaClass.kotlin //获取当前实体的类,Dao类的实现对象
        val tbName = kc.findAnnotation<Table>()?.name ?: javaClass.simpleName
        val map = linkedMapOf<String, Any?>().apply {
    
    
            kc.declaredMemberProperties.filter {
    
    
                it.annotations.isNotEmpty()
            }.forEach {
    
    
                it.findAnnotation<ID>()?.let {
    
     anno ->
                    put(anno.name, it.get(this@Dao))
                    return@forEach
                }
                it.findAnnotation<Field>()?.let {
    
     anno ->
                    put(anno.name, it.get(this@Dao))
                    return@forEach
                }
            }
        }
        val fields = map.keys.joinToString(",")
        val values = map.values.joinToString(",")
        val sql = "insert into $tbName ($fields) values ($values)"
        println(sql) //by JDBC send SQL to DB
        return true
    }

    override fun delete(): Boolean = false
    override fun update(): Boolean = false
    override fun select(): Boolean = false
}

//使用以上框架,用注解标记实体类,让实体类可以和框架进行交互
@Table("书")
class Book(
    @ID("编号") val id: Int,
    @Field("书名") val name: String,
    @Field("作者") var author: String
) : Dao<Book>()

fun main() {
    
    
    Book(1, "TAOCP", "knuth").add()
}

ClassKClass

import kotlin.reflect.KClass
import kotlin.reflect.full.createInstance
import kotlin.reflect.full.primaryConstructor

class Book(val id: Int, val name: String, var author: String)

fun main() {
    
    
    //获取类
    var kc: KClass<Book> = Book::class
    //kotlin、java的class转换
    var jc: Class<Book> = Book::class.java
    var kcName = Class.forName("java.lang.Object").kotlin

    //创建对象
    val obj = kcName.createInstance()
    //使用一级构造方法创建对象
    var book = kc.primaryConstructor?.call(1, "书名", "作者名") as Book

    //使用对象获取类
    var c = book::class
    jc = book.javaClass

    //获取构造方法,创建对象
    val constructor = ::Book
    book = constructor(1, "书名", "作者名")

    //获取对象的属性的值
    var name = Book::name.get(book)
    name = book::name.get()

    //修改对象的属性值
    Book::author.set(book, name)
    book::author.set(name)
}

函数类型

将函数作为字段,这个字段的类型为函数类型

Lambda表达式

本质是匿名内部类(只有一个抽象方法的函数式接口)。

为了将表达式当作变量进行传递时使用,避免手动创建多余的类。

//常量gcd是函数类型((Int, Int) -> Int)
//a, b是形参,形参 -> 语句(表达式)
val gcd: (Int, Int) -> Int = lambda@{
    
     a, b ->
    if (a == 0 || b == 0) return@lambda 0
    var t: Int
    var max: Int = kotlin.math.max(a, b)
    var min: Int = kotlin.math.min(a, b)
    while (max % min != 0) {
    
    
        t = max % min
        max = min
        min = t
    }
    return@lambda min
}

fun main() {
    
    
    val n = gcd(16, 60)
    println(n)
}

//只有一个参数it的Lambda表达式,有5种写法
val list = listOf(1, 2, 3)
list.forEach({
    
     it -> println(it) })
list.forEach({
    
     println(it) })
list.forEach() {
    
     println(it) }
list.forEach {
    
     println(it) }
list.forEach(::println)
fun main() {
    
    
    //Java基本类型使用Ref.IntRef类型封装
    //其他类型使用Ref.ObjectRef类型封装
    var n = 0
    val inc = {
    
    
        n++ //lambda内部访问外部变量
    }
    inc()
    println(n) //1
}

高阶函数

把Lambda表达式当作参数的函数。

//max是类型C的扩展(高阶)函数,返回类型R
//f是类型C的扩展函数,返回类型R
//调用函数f,传入两个I类型的变量,经过外部自定义的语句执行,返回类型R
fun <C, I, R> C.max(i1: I, i2: I, f: C.(a: I, b: I) -> R): R = f(i1, i2)

fun main() {
    
    
    //为String类型扩展了max函数
    //传入两个Int进行比较
    //自定义比较的函数当作参数传入
    val n = String.max(12, 15) {
    
     a, b -> if (a > b) a else b }
    println(n)
}

内联函数

属性使用const修饰可以成为常量,调用时由于已经初始化赋值,编译器可以直接拷贝值到调用处。

方法如果需要以拷贝的方式替换到调用处,可以使用内联函数。

inline

Lambda表达式作为函数的参数时,本质是创建了匿名内部类,然后创建出临时对象,传入形参。

为了避免性能损耗,可以使用内联函数,将代码拷贝到调用处展开,减少方法调用栈的层数(性能损耗忽略不计)。

如果内联函数的方法体太大,会导致字节码文件过大。

fun main() {
    
    
    for (i in 1..100) {
    
    
        task({
    
     println("准备") }, {
    
     println("善后") })
    }
}

inline fun task(preTask: () -> Unit, postTask: () -> Unit): Unit {
    
    
    preTask()
    println("执行")
    postTask()
}

编译时代码块替换到调用的位置,不用直接调用task

...
println("准备")
println("执行")
println("善后")
...

如果不加inline,每次调用task方法,都会创建函数对象(kotlin.jvm.functions.Function0)传入task方法中用完就丢,不适合频繁调用的场景。

//特殊的使用场景(去掉调用方法前边的限定类)
import java.lang.Math as M
public inline fun min(a: Int, b: Int): Int = M.min(a, b)
public inline fun max(a: Int, b: Int): Int = M.max(a, b)
fun main() {
    
    
    println(max(1, 2))
}

noinline

如果只是将代码块替换到调用的位置,会有意外情况出现。

fun main() {
    
    
    val task = task({
    
     println("准备") }, {
    
     println("善后") })
    task.invoke()
}

inline fun task(preTask: () -> Unit, noinline postTask: () -> Unit): () -> Unit {
    
    
    preTask()
    println("执行")
    postTask()
    //去掉return的"postTask"替换到调用位置后,成为孤立的存在
    //在形参上加上"noinline",就不参与内联,形参被保留
    return postTask
}

关闭内联优化,使函数类型的形参可以当作对象继续使用。

crossinline

fun main() {
    
    
    task(
        {
    
    
            println("准备")
            //不会中断后续的执行
            return@task
        },
        {
    
    
            println("善后")
            //内联后会中断后续的执行
            //添加"crossinline"后,这里不能直接“return”
            /*return*/
            //避免外部调用时错误的使用return
            return@task
        }
    )
    println("结束")
}

inline fun task(preTask: () -> Unit, crossinline postTask: () -> Unit) {
    
    
    preTask()
    println("执行")
    postTask()
}

Lambda表达式里不允许使用return,除非是内联函数的参数,这样参数必须加crossinline关键字,使其可以被间接调用。

使用内联函数的注意事项

reified

泛型的类型在编译后会被类型擦除。为了避免无法进行正常的类型判断,使用reified标记,函数必须是内联函数。

open class Animal //动物
open class Dog : Animal() //狗
open class Shiba : Dog()//柴犬

//必须使用内联函数,方法体将拷贝到调用处,将T替换为正确的类型,避免被编译时类型擦除
inline fun <reified T> get(list: List<Animal>): T? {
    
    
    //类型擦除T后,这里无法判断
    list.forEach {
    
     t -> if (t is T) return t }
    return null
}

fun main() {
    
    
    val list = listOf<Animal>(Dog(), Shiba())
    val dog = get<Dog?>(list)
    println(dog)
    val shiba = get<Shiba?>(list)
    println(shiba)
}

this

class A {
    
    
    inner class B {
    
    
        fun f1() {
    
    
            this@A.ptl("1") //类A
            this@B.ptl("2") //类A$B
        }
        val f2 = {
    
     s: String ->
            this.ptl("3") //A$B
        }
    }
    fun f() {
    
    
        val b = B()
        b.f1()
        b.f2("123")
    }
}
fun f() {
    
    
    val f = fun String.() {
    
    
        this.ptl("4") //abc
    }
    f("abc")
}
fun Int.f() {
    
    
    this.ptl("5") //1
    this@f.ptl("6") //1
}

fun main() {
    
    
    val a = A()
    a.f()
    f()
    1.f()
}
fun Any?.ptl(s: String) {
    
    
    println("$s $this")
}

Kotlin与Java互动

Kotlin 中调用 Java - Kotlin 语言中文站 (kotlincn.net)

Kotlin调用Java

关键字冲突

import org.jetbrains.annotations.NotNull;

import java.util.concurrent.Callable;

public class Main {
    
    
    @NotNull //使用JSR-305限制null
    public static String when(@NotNull Callable call) throws Exception {
    
    
        call.call();
        return "OK";
    }
}
fun main() {
    
    
    //when在Java中不是关键字,在Kotlin中是关键字
    //需要使用反引号修饰
    //返回类型可能为null,必须确认这个可能
    val str: String? = Main.`when` {
    
     println("Hello Java") }
    println(str)
}

避免使用Any的扩展函数,读取代码时很难分清调用的是哪个方法。

bean规范

public class User {
    
    
    private String name;
    private boolean admin;
    private boolean student;

    public void setName(String name) {
    
    
        this.name = name;
    }

    public void hasAdmin(boolean admin) {
    
    
        this.admin = admin;
    }

    public void setStudent(boolean student) {
    
    
        this.student = student;
    }

    public String getName() {
    
    
        return name;
    }

    public boolean isAdmin() {
    
    
        return admin;
    }

    public boolean getStudent() {
    
    
        return student;
    }
}
fun main() {
    
    
    val u = User()
    //使用get和set,直接使用属性名进行访问
    u.name = "张三"
    println(u.name)
    //使用is和has,以Getter的方法名为主
    u.hasAdmin(true)
    println(u.isAdmin)
    //使用get和set
    u.student = true
    println(u.student)
    //由于Kotlin规定所有属性都是可读,只有Setter的属性不会出现
}

建议使用getXxxsetXxx的命名规范。

运算符重载

import java.math.BigInteger;

public class Integer {
    
    
    private final BigInteger b;

    public Integer(BigInteger b) {
    
    
        this.b = b;
    }

    //Kotlin的运算符重载,自动匹配了这个方法
    public Integer plus(Integer other) {
    
    
        return new Integer(b.add(other.b));
    }

    @Override
    public String toString() {
    
    
        return b.toString(10);
    }

    public String toString(int radix) {
    
    
        return b.toString(radix);
    }
}
import java.math.BigInteger

fun main() {
    
    
    val a = Integer(BigInteger.ONE)
    val b = Integer(BigInteger.TWO)
    val c = a + b
    println(c)//3
}

函数表达式的传递问题

fun main() {
    
    
    val opt = Optional<Runnable>();
    val lambda: () -> Unit = {
    
    
        println("Hello")
    }
    //SAM转换的存在,每次都会将Lambda表达式封装为对象,值不一样
    println(lambda.hashCode())
    opt.add(lambda)
    opt.add(lambda)
    opt.add(lambda)
    opt.remove(lambda)
    opt.remove(lambda)
    opt.remove(lambda)
}
import java.util.ArrayList;

public class Optional<T> {
    
    
    private final ArrayList<T> list = new ArrayList<>();

    public void add(T t) {
    
    
        list.add(t);
        System.out.println("add:" + t.hashCode() + ",size:" + list.size());
    }

    public void remove(T t) {
    
    
        list.remove(t);
        System.out.println("remove:" + t.hashCode() + ",size:" + list.size());
    }
}

Java调用Kotlin

类成员

data class Person(
    //var标记后,有get和set方法
    var name: String,
    @JvmField //标记后没有get和set方法,并且public
    var age: Int
)
public class Main {
    
    
    public static void main(String[] args) {
    
    
        Person p = new Person("张三", 16);
        System.out.println(p.getName());
        p.setName("三张");
        p.age = 17;
        System.out.println(p);
        //调用kotlin时需要注意null
    }
}

单例

object Person{
    
    
    fun hello(){
    
    
        println("Hello Java")
    }
}
public class Main {
    
    
    public static void main(String[] args) {
    
    
        Person.INSTANCE.hello();
    }
}

默认参数

object Overloads {
    
    
    //加上注解后就拥有了多个重载的方法
    @JvmOverloads
    fun sum(a: Int, b: Int = 0, c: Int = 0): Int {
    
    
        return a + b + c
    }
}
public class Main {
    
    
    public static void main(String[] args) {
    
    
        int r = Overloads.INSTANCE.sum(1, 2, 3);
        System.out.println(r);
        r = Overloads.INSTANCE.sum(5);
        System.out.println(r);
    }
}

包方法

//Package.kt
fun hello() {
    
    
    println("Hello")
}
public class Main {
    
    
    public static void main(String[] args) {
    
    
        PackageKt.hello();
    }
}

扩展方法

//Package.kt
fun String.isEmpty(): Boolean {
    
    
    return this == ""
}
public class Main {
    
    
    public static void main(String[] args) {
    
    
        boolean b = PackageKt.isEmpty("");
        System.out.println(b);
    }
}

internal

在Kotlin规则中,被internal修饰后,跨越模块则不能访问。如果被Java调用,修饰Kotlin的类,对于Java处处可用,修饰Kotlin的类成员,无法访问。

协程

Kotlin的协程本质还是线程。不用过多关心线程也可以写出复杂的并发操作。在同一个代码块里进行灵活的线程切换。

使用协程需要引入kotlinx-coroutines依赖。

本质

协程是使用线程封装出的工具包框架。

fun main() = runBlocking {
    
    
    val exec = Executors.newSingleThreadScheduledExecutor()
    val task = Runnable {
    
     print("|") }
    repeat(10) {
    
    
        exec.schedule(task, 1, TimeUnit.SECONDS)
    }

    repeat(10) {
    
    
        launch {
    
    
            delay(1000L)
            print("-")
        }
    }
}

创建

import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
import java.util.concurrent.Executors
import kotlin.concurrent.thread

fun main() = runBlocking {
    
    
    //创建线程
    Thread {
    
    
        Thread.sleep(5 * 1000L)
        println("${
      
      Thread.currentThread()} Thread")
    }.start()
    thread() {
    
    
        Thread.sleep(5 * 1000L)
        println("${
      
      Thread.currentThread()} thread")
    }
    val exec = Executors.newCachedThreadPool()
    exec.execute {
    
    
        Thread.sleep(5 * 1000L)
        println("${
      
      Thread.currentThread()} Executors")
    }
    //创建协程
    launch {
    
    
        delay(5 * 1000L)
        println("${
      
      Thread.currentThread()} 协程")
    }
    println("${
      
      Thread.currentThread()} main")
}

非阻塞式

所有的代码本质都是阻塞式的,最基本的一条汇编指令执行时都需要时间,而且执行时不能被其他汇编指令干扰,只是速度快到无法感知的纳秒级别,认为代码是非阻塞式的。如果遇到人可以感受卡顿,这时就认为是阻塞式代码导致的。

借助Kotlin语法优势,写出看似同步却是异步的过程,都在同一个代码块中,而且还不会“卡”当前线程,这就是非阻塞式。只要执行代码时不被耗时的过程卡住,创建个线程,把耗时过程交给另一个线程去做,当前线程就是非阻塞式。

import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking

fun main() {
    
    
    runBlocking {
    
    
        launch(Dispatchers.IO) {
    
    
            TODO("保存文件") //将任务切换到后台
        }
        launch(Dispatchers.Main) {
    
    
            TODO("更新数据") //将任务切换到前台
        }
        launch(Dispatchers.Default) {
    
    
            TODO("更新UI等复杂运算,其他调度器的任务")
        }
    }
}

将不同线程的代码写在同一个源文件中,避免大量的回调嵌套过程,甚至调用的顺序问题。

import kotlinx.coroutines.async
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking

fun main() = runBlocking {
    
    
    //async:也是创建协程的方式
    //先获取name和先获取age不影响后续代码逻辑
    var name: String = ""
    async {
    
    
        //模拟网络IO阻塞,线程并没有等待
        //而是缓慢的处理(不停的循环判断结束的条件)
        /*api.getName(user)*/delay(5000L)
        name = "张三"
    }
    var age: Int = 0
    async {
    
    

        /*api.getAge(user)*/delay(3000L)
        age = 18
    }
    //主线程没有被阻塞,main直接把下面的代码挂起
    //交给上面两个线程执行完各自的过程后恢复,再走完下面的过程
    launch {
    
    
        val info = "{name=$name,age=$age}"
        println(info)
    }
    println("任务启动,等待结果")
}

协程的使用场景:当需要指定特定的线程去执行耗时的过程。

fun main() = runBlocking {
    
    
    launch(Dispatchers.Main) {
    
    
        //withContext:切换线程执行,然后自动切回原先的线程
        val data = withContext(Dispatchers.IO) {
    
    
            TODO("读取数据")
        }
        TODO("展示数据")
    }
}

suspend

当需要切换线程去执行时,如果将这部分代码封装成函数,需要加suspend关键字,成为挂起函数。

挂起函数中的过程主要是很耗时的计算或者IO操作,这些操作不适合主线程去完成,需要切换线程去执行(挂起的本质),执行完毕后再切回来(被挂起后的恢复)。

import kotlinx.coroutines.*

fun main() = runBlocking {
    
    
    launch(Dispatchers.Default) {
    
    
        //被调用的挂起函数交给其他指定的线程执行,然后恢复后继续向下执行
        val data = loadData()
        println("${
      
      Thread.currentThread()} 展示数据开始") //step:3
        delay(3000L)
        println("${
      
      Thread.currentThread()} 展示数据完成") //step:3
    }
    println("${
      
      Thread.currentThread()} 任务启动完成") //step:1
}

//挂起函数,线程调用时不会被阻塞,继续往下执行
//线程切换是在withContext代码中,suspend只是起对调用者的提醒作用
//提醒这是个很耗时的过程,在主线程谨慎调用,避免卡顿
suspend fun loadData(): Any = withContext(Dispatchers.IO) {
    
    
    println("${
      
      Thread.currentThread()} 加载数据开始") //step:2
    delay(3000L)
    println("${
      
      Thread.currentThread()} 加载数据完成") //step:2
}

挂起函数必须在协程里面被调用,或者在另一个挂起函数里被调用,这样执行完挂起函数的时候,可以自动把线程切回来。如果在普通线程里调用,执行完就切不回来了(无法恢复被挂起的线程)。

非阻塞式

所有的代码本质都是阻塞式的,最基本的一条汇编指令执行时都需要时间,而且执行时不能被其他汇编指令干扰,只是速度快到无法感知的纳秒级别,认为代码是非阻塞式的。如果遇到人可以感受卡顿,这时就认为是阻塞式代码导致的。

借助Kotlin语法优势,写出看似同步却是异步的过程,都在同一个代码块中,而且还不会“卡”当前线程,这就是非阻塞式。只要执行代码时不被耗时的过程卡住,创建个线程,把耗时过程交给另一个线程去做,当前线程就是非阻塞式。

import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking

fun main() {
    
    
    runBlocking {
    
    
        launch(Dispatchers.IO) {
    
    
            TODO("保存文件") //将任务切换到后台
        }
        launch(Dispatchers.Main) {
    
    
            TODO("更新数据") //将任务切换到前台
        }
        launch(Dispatchers.Default) {
    
    
            TODO("更新UI等复杂运算,其他调度器的任务")
        }
    }
}

将不同线程的代码写在同一个源文件中,避免大量的回调嵌套过程,甚至调用的顺序问题。

import kotlinx.coroutines.async
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking

fun main() = runBlocking {
    
    
    //async:也是创建协程的方式
    //先获取name和先获取age不影响后续代码逻辑
    var name: String = ""
    async {
    
    
        //模拟网络IO阻塞,线程并没有等待
        //而是缓慢的处理(不停的循环判断结束的条件)
        /*api.getName(user)*/delay(5000L)
        name = "张三"
    }
    var age: Int = 0
    async {
    
    

        /*api.getAge(user)*/delay(3000L)
        age = 18
    }
    //主线程没有被阻塞,main直接把下面的代码挂起
    //交给上面两个线程执行完各自的过程后恢复,再走完下面的过程
    launch {
    
    
        val info = "{name=$name,age=$age}"
        println(info)
    }
    println("任务启动,等待结果")
}

协程的使用场景:当需要指定特定的线程去执行耗时的过程。

fun main() = runBlocking {
    
    
    launch(Dispatchers.Main) {
    
    
        //withContext:切换线程执行,然后自动切回原先的线程
        val data = withContext(Dispatchers.IO) {
    
    
            TODO("读取数据")
        }
        TODO("展示数据")
    }
}

suspend

当需要切换线程去执行时,如果将这部分代码封装成函数,需要加suspend关键字,成为挂起函数。

挂起函数中的过程主要是很耗时的计算或者IO操作,这些操作不适合主线程去完成,需要切换线程去执行(挂起的本质),执行完毕后再切回来(被挂起后的恢复)。

import kotlinx.coroutines.*

fun main() = runBlocking {
    
    
    launch(Dispatchers.Default) {
    
    
        //被调用的挂起函数交给其他指定的线程执行,然后恢复后继续向下执行
        val data = loadData()
        println("${
      
      Thread.currentThread()} 展示数据开始") //step:3
        delay(3000L)
        println("${
      
      Thread.currentThread()} 展示数据完成") //step:3
    }
    println("${
      
      Thread.currentThread()} 任务启动完成") //step:1
}

//挂起函数,线程调用时不会被阻塞,继续往下执行
//线程切换是在withContext代码中,suspend只是起对调用者的提醒作用
//提醒这是个很耗时的过程,在主线程谨慎调用,避免卡顿
suspend fun loadData(): Any = withContext(Dispatchers.IO) {
    
    
    println("${
      
      Thread.currentThread()} 加载数据开始") //step:2
    delay(3000L)
    println("${
      
      Thread.currentThread()} 加载数据完成") //step:2
}

挂起函数必须在协程里面被调用,或者在另一个挂起函数里被调用,这样执行完挂起函数的时候,可以自动把线程切回来。如果在普通线程里调用,执行完就切不回来了(无法恢复被挂起的线程)。

如果在挂起函数中没有线程切换(withContext)的操作,这个函数将只能在协程里被调用(必须加suspend关键字)。

同步

@Volatile
var count: Int = 0

@Synchronized
fun count() {
    
    
    synchronized(count) {
    
    
        count++
    }
}

猜你喜欢

转载自blog.csdn.net/a4019069/article/details/126814446