导读
适合群体: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进制。
数字和数字之间可以使用下划线_
分隔。
数组
Array
、IntArray
、UIntArray
…
字符串
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 ")
}
do
、while
用法
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]
}
Any、Unit、Nothing
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
循环的until
、downTo
、step
使用了中缀表达式。
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)
}
}
类的成员
类由多个字段和方法构成。
字段、属性
属性的声明由val
、var
标记,一级构造方法也是如此。
被abstract
修饰的抽象成员可以不用初始化赋值。
- 被
private
标记,没有set
、get
方法 - 被
val
标记,为属性,只有get
方法 - 被
var
标记,为属性,set
、get
方法都有
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
手动为属性添加get
和set
方法
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)
实例域
使用枚举时不推荐使用ordinal
、name
(序列化和反序列化出现差异时无法避免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
方法。
如果同时重载了plusAssign
、plus
运算符,执行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()
}
Class
、KClass
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的属性不会出现
}
建议使用getXxx
和setXxx
的命名规范。
运算符重载
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
}
挂起函数必须在协程里面被调用,或者在另一个挂起函数里被调用,这样执行完挂起函数的时候,可以自动把线程切回来。如果在普通线程里调用,执行完就切不回来了(无法恢复被挂起的线程)。文章来源:https://www.toymoban.com/news/detail-401095.html
非阻塞式
所有的代码本质都是阻塞式的,最基本的一条汇编指令执行时都需要时间,而且执行时不能被其他汇编指令干扰,只是速度快到无法感知的纳秒级别,认为代码是非阻塞式的。如果遇到人可以感受卡顿,这时就认为是阻塞式代码导致的。
借助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
关键字)。文章来源地址https://www.toymoban.com/news/detail-401095.html
同步
@Volatile
var count: Int = 0
@Synchronized
fun count() {
synchronized(count) {
count++
}
}
到了这里,关于【Kotlin】从Java转向Kotlin,耐心看完这篇博客就够了。Kotlin快速入门教程分享的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!