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

1,067 阅读15分钟

导读

适合Java已经入门的人来看,如果是零基础,不要勉强!虽然没有深奥的术语,即使有也是用我的理解和使用体验来解读。

多多利用目录导航,快速跳转。

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

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

将Kotlin生成的Class文件反编译为Java的源码,看看生成同样的Class文件,用Java会如何去实现。

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

build.gradle.kts

gradle版本:7.7.3

import org.jetbrains.kotlin.gradle.tasks.KotlinCompile

plugins {
    application
    kotlin("jvm") version "1.7.10"
}

application {
    mainClass.set("MainKt")
}

repositories {
    mavenCentral()
}

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

tasks.withType<KotlinCompile> {
    kotlinOptions.jvmTarget = "11"
}

基础篇

启动入口

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

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

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

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

方法默认为不可继承的静态方法,参数列表传入的值不能为null,add方法对应Java的方法如下。

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

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

数据类型

变量、常量

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

i = i + 1 //正常
n = n + 1 //错误

原始数据类型

Kotlin没有基本数据类型,只有封装Java基本类型后的原始数据类型。编译前都是包装的类型,编译时会进行判断(null的可能性检查)选择基本类型还是包装类型。

数值

类型位宽最小值最大值
Byte8-128127
Short16-3276832767
Int32-2,147,483,6482,147,483,647
Long64-9,223,372,036,854,775,8089,223,372,036,854,775,807
  • 整型默认为Int

  • Long需要加尾缀L

浮点

类型位宽有效位数指数位数最小值最大值
Float322481.4e-453.4028235e38
Double6453114.9e-3241.7976931348623157e308
  • 浮点型默认为Double
  • Float需要加尾缀F
  • e标记的前后必须为10进制数,e表示+10^,不支持Java的p标记

字符

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

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

var a: Char = 'A'
var b: Char = (66).toChar()
var c: Char = Char(67)
var d: Int = a.code //借助code转换
println(b) //B
println(c) //C
println(d) //65

布尔

布尔类型范围
Booleanfalse, true

无符号数字

类型位宽最大值
UByte8255
UShort1665535
UInt324,294,967,295
ULong6418,446,744,073,709,551,615
var a = 200UL // ULong
println(a) //200
var b: UByte = (200).toUByte() //有符号型转无符号
println(b) //200
var c = a * b
println(c) //40000

  • 无符号类型需要加尾缀U
  • 无符号类型的变量不能直接与有符号类型的变量运算

进制

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

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

数组

ArrayIntArrayUIntArray

字符串

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

数值类型的自动转换

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

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

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

表达式和语句

// 这是条语句,其中 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

var max: Int
if (a > b) {
    max = a
} else if (a < b) {
    max = b
} else {
    TODO("一样大,什么也不做")
}

表达式用法

var a = 1
var b = 2
val max = if (a > b) a else b
println(max)

var a = 1
var b = 2
val max = if (a > b) {
    println("Choose a")
    a //缺省return的返回
} else {
    println("Choose b")
    b
}
println(max)

when

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

fun test(v: Int) {
    when (v) {
        1, 2, 3, 4, 5, 6 -> {
            println("工作日")
        }
        //不会穿层执行代码,每个条件都有break跳出分支
        7 -> println("休息日")
        else -> {
            println("非法输入")
        }
    }
}

借助Set集合的方式进行匹配,使用setOf方法创建Set对象。

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

原理是比较HashCode值,所以Set集合是无序。

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

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

判断数据类型

//when可以作为表达式
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

(10 downTo 0 step 3).forEach { i ->
    // 10 7 4 1
    print("$i ")
}

dowhile

传统用法

while(布尔表达式) {
    //循环内容
}
do {
    //代码语句
}while(布尔表达式);

@

跳出到指定代码块的标签

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

null

?

任何数据类型后面加?,这个类型可以为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时,强制转换为String
//不支持从父类强制转换为子类
//转换失败,结果为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
}

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

var str: String? = "123"
//如果为null,执行右边的代码退出(或者抛异常)
var s = str ?: return
println(s?.length) //3

var str: String? = "123"
//使用?:避免null引发后续代码的执行
//后续代码做好不正常结果的处理
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[]
var arr1:Array<Int> = arrayOf(1,2,3) //Array<T>的arrayOf方法
//int[]
var arr2 = intArrayOf(1,2,3)

Kotlin不使用Java的数据类型系统,重新设计的原始数据类型使用时要比Java方便。

访问修饰符

修饰类和顶层成员

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

修饰类的成员

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

访问权限的传递

继承时访问权限时,访问修饰符的访问范围不能被放大。

数组

创建

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

常用方法

map

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

fun main() {
    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}
}

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

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

以数组的元素为键,存放特定的值,键相同时会不存放。

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

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

KotlinJava
AnyObject
UnitVoid
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}

调用Java的集合

public class JavaList {
    public static List<Integer> list() {
        return Arrays.asList(null, 1, 2);
    }
}

val list = mutableListOf<Int>()
//Java 11 => Jvm class version 55
JavaList.list().forEach { v ->
    list.add(v)//java.lang.NullPointerException
}

常用方法

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()
    .filter { n -> n > 4}//[5,6,7,8,9,a],不执行
    .map { n -> n * 2 }//[a,c,e,10,12,14],不执行
    //开始执行序列设计好的上述过程
    //每个元素都会经过上述操作,避免中间创建临时对象
    .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())//能获取,能传入
}

方法

顶级方法

定义在源文件内类的外部的方法,Java不支持,编译后顶级方法出现在以源文件名为类的内部,类名可以设置。

//设置源文件的类名,Java调用Kotlin时,使用这个类名
@file:JvmName("Main")

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

fun main() {
    "123".ptl()
}

可选参数

//参数拥有默认值后,变为可选参数,不需要赋值也可以使用
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 n: Int, n1: Int): IntArray {
    val arr = n.copyOf(n.size + 1)
    arr[n.size] = n1
    return arr
}

*

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

val arr = arrayOf(1, 2, 3)
listOf(arr).run { println("$size") } //1
listOf(*arr).run { println("$size") } //3

infix

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

infix fun Int.max(that: Int) = this.coerceAtLeast(that)
fun main() {
    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(a: Int, b: Int): Int { a.coerceAtLeast(b) })(5, 6).ptl()//6

进阶篇

构造方法

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

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

//一级构造方法的参数如果没有被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)
}

匿名内部类对象

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

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

init

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

//会添加到一级构造方法中,每次创建对象时最先执行
//如果没有一级构造方法,则添加到每一个二级构造方法中
//如果在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)
    }
}

类的成员

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

字段、属性

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

  • 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(args: Array<String>) {
    val p = Person()
    val fields = p.javaClass.declaredFields
    for (field in fields) {
        println(field.name)
    }
}

field

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

const

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

//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')"
    }
}

委托

by

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

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

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

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

fun main(args: Array<String>) {
    var p = Person()
    p.name = "123"
    println(p.toString())
}

by lazy

class Person {
    //延迟加载的属性只能使用val修饰
    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

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

class Person {
    //设置初始值
    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(args: Array<String>) {
    val p = Person()
    p.name = "A"
    p.name = "B"
    println(p.toString())
}

by map

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

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

fun main(args: Array<String>) {
    val p = Person(hashMapOf("id" to 1, "name" to "a"))
    println(p.toString())
}

委托模式

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

interface People {
    fun talk()
    fun run()
}

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

    override fun run() {
    }
}

//Woman使用代理对象man实现接口,实现和Man类完全一致
class Woman(privarte val man: Man = Man()) : People by man
/*
class Woman(privarte val man: Man = Man()) : People {
    override fun talk() {
        man.talk()
    }

    override fun run() {
        man.run()
    }
}
*/

枚举

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

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))
}

密封类和枚举对比

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

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

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

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

常用方法

//枚举可以实现接口,不能继承类
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获取枚举值,可能为null
    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(001),
    KR(101),
    JP(102);
}

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

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

EnumMapEnumSet

Enum集合对HashSetHashMap进行了优化,内存占用小,性能高。

操作符重载

org.jetbrains.kotlin.ir.backend.js.utils.OperatorNames

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 (this.q < 0) {
            this.q = -this.q
            this.p = -this.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
        }
    }

    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
}

一元操作符

操作符函数名接口方法
+ aunaryPlusoperator fun unaryPlus(): T
- aunaryMinusoperator fun unaryMinus(): T
! anotoperator fun not(): Boolean
++aincoperator fun inc(): Nothing = TODO()
a++incoperator fun inc(): Nothing = TODO()
--adecoperator fun dec(): Nothing = TODO()
a--decoperator fun dec(): Nothing = TODO()

基本运算符

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

复合赋值操作运算符

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

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

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

按位操作符

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

操作符函数名接口方法示例
a & ba and binfix fun and(t: T): T
`ab`a or binfix fun or(t: T): T
~ aa inv boperator fun inv(): T
a ^ ba xor binfix fun xor(t: T): T
a << ba shl binfix fun shl(t: T): T
a >> ba shr binfix fun shr(t: T): T
a >>> ba ushr binfix fun ushr(t: T): T

比较操作符

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

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

索引操作符

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

调用函数符

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

in操作符

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

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

区间操作符

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

解构操作符

操作符接口方法示例
(a, b, c...)component1component2component3...
  • 两个返回值元组:kotlin.Pair
  • 三个返回值元组:kotlin.Triple
//Fraction
operator fun component1(): Int {
    return this.p
}
operator fun component2(): Int {
    return this.q
}
//main
val f = Fraction(1, 3)
//a=>component1,b=>component2
val (a, b) = f

文件

读写

fun main() {
    //文件不存在会自动创建
    val write = File("C:\\Users\\kinlon\\Desktop\\1.txt")
    //覆盖
    write.writeBytes("123".toByteArray(Charsets.UTF_8))
    write.writeText("12") //默认使用UTF-8编码
    //追加
    write.appendBytes("12".toByteArray())
    write.appendText("12")
    //使用Java的IO流,完全覆盖
    write.outputStream().use {
        it.write("abc".toByteArray())
    }

    //文件不存在,抛出FileNotFoundException
    val read = File("C:\\Users\\kinlon\\Desktop\\2.txt")
    //按行读取
    ptl { sb ->
        read.forEachLine { sb.append(it).append('\n') }
    }
    //按数组块大小读取
    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.toChar())
                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)
}

遍历

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()
    }

拷贝

val file = File("C:\\Users\\kinlon\\Desktop\\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
import java.io.FileNotFoundException
import java.io.IOException

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

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

    val str = ""
    val n = try {
        str.toInt() //根据代码逻辑需要捕获异常
    } catch (e: NumberFormatException) {
        println(e)
        -1
    } finally {
        println("$str is empty, default value -1")
    }
    println(n)
}

//被Java调用需要捕获检查性异常时,使用注解
@Throws(IOException::class)
fun exists(path: String): File {
    val file = File(path)
    if (file.exists().not()) {
        throw FileNotFoundException("不存在的路径:$path")
    }
    return file
}

高级篇

注解

元注解

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

//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注解

注解名作用
@VolatileJava关键字volatile
@StrictfpJava关键字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

//获取类
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
}

val n = gcd(16, 60)

//只有一个参数it,有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)

//Java基本类型使用Ref.IntRef类型封装
//其他类型使用Ref.ObjectRef类型封装
var n = 0
val inc = {
    n++ //lambda内部访问外部变量,可以不是final
}
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)
}

内联函数

inline

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

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

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

fun main() {
    ptl {
        it.append("高阶").append("函数")
    }
    ptl {
        it.append("内联").append("函数")
    }
}
inline fun ptl(f: (sb: StringBuilder) -> String): String {
    val str = StringBuilder()
    return f.invoke(str) //f(str)
}

编译后效果,代码展开为一个函数。

fun main() {
    println(print@{
        val str = StringBuilder()
        return@print str.append("高阶").append("函数").toString()
    })
    println(print@{
        val str = StringBuilder()
        return@print str.append("内联").append("函数").toString()
    })
}

noinline

fun main() {
    hello({ println("pre") }, { println("post") })
    
    //内联函数展开后
    println("pre")
    println("hello")
    //内联函数如果返回函数类型
    //必须解除内联,否则代码展开后
    //postAct形参不存在
    return postAct
    //加上noinline,就正常了
    val postAct = ({ println("post") }.invoke())
    postAct
}

inline fun hello(preAct: () -> Unit, noinline postAct: () -> Unit): () -> Unit {
    preAct()
    println("hello")
    postAct()
    return postAct
}

crossinline

fun main() {
    hello({ println("pre") }, { println("post") })
}

inline fun hello(preAct: () -> Unit, crossinline postAct: () -> Unit) {
    preAct()
    println("hello")
    Thread {
        postAct() //添加crossinline后,可以被别的线程间接调用
        return@Thread //不可以直接return
    }
}

使用内联函数的注意事项

fun main() {
    hello({ println("pre") }, post@{
        println("post")
        //不建直接添加return,加跳转标签
        return@post
    })
    println("return")//上边直接return,这里不执行
}

inline fun hello(preAct: () -> Unit, postAct: () -> Unit) {
    preAct()
    println("hello")
    postAct()
}

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

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

本质

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

fun main() = runBlocking {
    repeat(100) {
        launch {
            delay(1000L)
            print(".")
        }
    }
    
    val exec = Executors.newSingleThreadScheduledExecutor()
    val task = Runnable { print(".") }
    repeat(100) {
        exec.schedule(task, 1, TimeUnit.SECONDS)
    }
}

创建

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

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

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

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

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


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

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

suspend

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

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

fun main() = runBlocking {
    launch(Dispatchers.Default) {
        //被调用的挂起函数交给其他指定的线程执行,然后恢复后继续向下执行
        val data = loadData()
        sleep(3000)
        println("${Thread.currentThread()} 展示数据") //step:3
    }
    println("${Thread.currentThread()} 任务启动完成") //step:1
}
//挂起函数,线程调用时不会被阻塞,继续往下执行
//线程切换是在withContext代码中,suspend只是起对调用者的提醒作用
//提醒这是个很耗时的过程,在主线程谨慎调用,避免卡顿
suspend fun loadData(): Any = withContext(Dispatchers.IO) {
    sleep(3000)
    println("${Thread.currentThread()} 加载数据") //step:2
}

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

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

本文转自 blog.csdn.net/a4019069/ar…,如有侵权,请联系删除。