guía
Grupo adecuado: personas que ya han iniciado Java, si no tiene base, ¡no lo fuerce! Si bien no hay jerga esotérica, si la hay, trate de que sea lo más fácil de entender posible.
Tanto Kotlin como Java son lenguajes JVM y, si es posible, se pueden guardar las mismas partes (espacio limitado), centrándose en Kotlin.
Los comentarios para el código de muestra son importantes. Lo mejor es utilizar herramientas de desarrollo como IDEA para ejecutarlo.
Al final, no es fácil de crear. Todos ellos son más de 50,000 palabras de notas escritas por mí mismo. Si crees que es útil para ti aprender Kotlin, sigue repitiendo (me gusta, sigue y recompensa). Esto es mi motivación creativa.
construir.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")
}
Tabla de contenido
- guía
- Básico
- Avanzado
- Avanzado
Básico
inicio de entrada
principal
fun main(args: Array<String>) {
println("Hello World")
}
Tanto el método sin parámetros main
como el método con parámetros pasados main
son los métodos de entrada para el inicio del programa kotlin.
Al inicio, primero se llama al método con parámetros main
y luego se llama al método sin parámetros main
.
fun main() {
println("Hello World")
}
Aunque main
pueden existir dos métodos al mismo tiempo, el método sin parámetros main
no se ejecutará.
nota
Las anotaciones de Kotlin son consistentes con Java
// 单行注释
/*
多行注释
*/
/**
文档注释,KDoc,详见:dokka
*/
método
El método predeterminado es un método estático no heredable y el valor pasado en la lista de parámetros no se puedenull
//关键字 fun
//形参名: 类型
//fun 方法名(参数列表): 返回值类型 {方法体}
fun add(n1: Int, n2: Int): Int {
return n1 + n2
}
El método add corresponde al método Java de la siguiente manera.
public static final int add(int n1, int n2) {
return n1 + n2;
}
Si necesita eliminar final
(herencia y acceso a subclases), puede usar open
la modificación de palabras clave fun
.
class Main {
open fun add(n1: Int, n2: Int): Int {
return n1 + n2
}
}
Cuando el método tiene solo una línea, puede usar =
la alternativa{...}
fun add(n1: Int, n2: Int): Int = n1 + n2
tipo de datos
variable, constante
//变量
var i: Int = 1
//常量
val n: Int = 2
i = i + 1 //OK
n = n + 1 //ERROR, Val cannot be reassigned
tipo de datos primitivo
Kotlin no tiene tipos de datos básicos , solo tipos de datos primitivos que encapsulan tipos básicos de Java.
Antes de la compilación, todos son tipos empaquetados.Al compilar, null
se hará un juicio (comprobación de posibilidad) para elegir el tipo básico o el tipo empaquetado.
entero
tipo | ancho de bit | valor mínimo | valor máximo |
---|---|---|---|
Byte |
8 | -128 | 127 |
Short |
dieciséis | -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 |
-
Entero por defecto es
Int
-
Long
Los enteros de tipo deben tener sufijoL
(deben estar en mayúscula)
punto flotante
tipo | ancho de bit | Dígitos significantes | Dígitos exponentes | valor mínimo | valor máximo |
---|---|---|---|---|---|
Float |
32 | 24 | 8 | 1.4e-45 | 3.4028235e38 |
Double |
64 | 53 | 11 | 4.9e-324 | 1.7976931348623157e308 |
- Flotante por defecto es
Double
Float
Necesito agregar sufijoF
e
El anverso y el reverso de la marca deben ser números decimales,2e3
lo que significa que la marca2*10^3
de Java no es compatiblep
personaje
personaje | ancho de bit | Rango de precisión |
---|---|---|
Char |
dieciséis | \u0000~\uFFFF |
El tipo Char desaprueba todos los métodos de conversión a números.
var a: Char
var b: Int
a = 'A'
//Int -> Char
a = (65).toChar()
a = Char(65)
//Char -> Int
a = 'A'
b = a.code
booleano
tipo booleano | alcance |
---|---|
Boolean |
false ,true |
número sin firmar
tipo | ancho de bit | valor máximo |
---|---|---|
UByte |
8 | 255 |
UShort |
dieciséis | 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
- Los tipos sin firmar requieren un sufijo
U
- Las variables de tipo sin signo no se pueden operar directamente con variables de tipo con signo
hexadecimal
El valor predeterminado es decimal, el prefijo hexadecimal es 0x
, el prefijo binario es 0b
, no se admite octal.
Los números se pueden _
separar con guiones bajos.
formación
Array
、IntArray
、UIntArray
…
cadena
String
、"字符串"
、"""原始字符串"""
Conversión automática de tipos numéricos
El rango de precisión del tipo de datos original está dentro del rango del tipo de datos de destino y se puede convertir automáticamente. De lo contrario, se convertirá a un valor incorrecto debido a la pérdida de precisión.
//Long转Int不能强制转换
var a: Int = 129L; //ERROR
//Int转Long自动转换
var b: Long = 129;
//超过最大值,最高位的进位丢失
var c = b.toByte() //-127
Al realizar operaciones en diferentes tipos, se convierte automáticamente a un tipo con un rango mayor antes de realizar operaciones (las operaciones solo se pueden realizar en el mismo tipo).
var a: Int = 129;
var b: Long = 129L;
var c = a * b //Long
println(c) //16641
expresiones y declaraciones
//这是条语句,其中 n = 1 是表达式
var n = 1
Las expresiones tienen un valor de retorno, las declaraciones no (equivalente al Vacío de Java).
Una instrucción es la unidad más pequeña de ejecución de un programa.
var n //声明语句
n = 1 //赋值语句(表达式加换行符)
n++ //自增语句
println(n) //方法调用语句
También hay declaraciones de creación de objetos y declaraciones de flujo de control.
declaración de flujo de control
No compatible goto
.
if
uso
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
Se ha mejorado el uso similar , Java 14, 17 y 18, y ahora el uso switch
de Java y Kotlin when
es similar.
val n = when (val s = readlnOrNull()) {
null -> 0
else -> s.length
}
println("输入了${
n}个字符")
Úsalo Range
para combinar.
fun main() {
test(2)
}
fun test(v: Int) {
when (v) {
in 1..5 -> {
println("工作日")
} //不用担心break
in setOf(6, 7) -> println("休息日")
!in 1..7 -> {
println("非法输入")
}
}
}
El principio de usar Set
un conjunto para hacer coincidir es comparar HashCode
valores, por lo que Set
el conjunto no está ordenado.
fun main() {
test("绿", "红") //黄
}
fun test(a: String, b: String) {
when (setOf(a, b)) {
setOf("红", "绿") -> {
println("黄")
}
setOf("红", "蓝") -> {
println("紫")
}
setOf("蓝", "绿") -> {
println("青")
}
else -> {
println("非法输入")
}
}
}
coincidencia de tipos de datos
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
uso
while (/*布尔表达式*/true) {
TODO("循环内容")
}
do {
TODO("循环内容")
} while (/*布尔表达式*/true)
label@
Saltar a la etiqueta del bloque de código especificado
val gcd = lambda@{
w1@ while (true) {
while (true) {
break
continue@w1
return@lambda
}
}
}
nulo
Type?
Cualquier tipo de datos se agrega después de ?
, lo que indica que el valor de este tipo se puedenull
var a: Int? //可以为null
var b: Int //不可以为null
Un tipo que puede ser null
no es asignable a null
un tipo que no puede ser
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)
}
posible null
método requiere
var str: String? = null
//必须加?,返回值类型为Int?
var len = str?.length
println(len) //null
?:
Puede ser null
un tipo de datos que puede utilizar Elvis
operadores.
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
Trate de evitar el uso de non- null
asertion( ), lo que destruirá la intención del diseño original de !!
kotlin pair .null
var str: String? = "123"
str?.let {
//let:只有在str非null时执行
//避免直接调用str.length出现异常
//也避免了执行str?.length,出现不正常的结果
//it是默认传入的参数,不为null
println(it.length) //3
}
O salga directamente sin ejecutar códigos subsiguientes para garantizar que no se ejecuten resultados anormales y pasos redundantes.
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
}
Tipo de paquete y tipo base
Al compilar, si el parámetro se ?
modifica y null
existe la posibilidad, será un tipo de paquete en caja después de la compilación; de lo contrario, se optimizará y compilará automáticamente en un tipo de datos básico.
//int
var a: Int = 1
//Integer
var b: Int? = null
arrayOf
Las colecciones y las matrices utilizan los tipos de contenedor de Java de forma predeterminada o los tipos primitivos de Java si se utiliza un tipo de método específico .
//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)
modificador de acceso
Clases de decoración y miembros de primer nivel
modificador | Ámbito de acceso máximo de Java | Ámbito de acceso máximo de Kotlin |
---|---|---|
public |
global | global |
protected |
mismo paquete y todos los subpaquetes | no apoyo |
internal |
no apoyo | mismo modulo |
default ,por defecto |
mismo paquete | global |
private |
Archivo fuente | Archivo fuente |
miembro de la clase condecorada
modificador | Ámbito de acceso máximo de Java | Ámbito de acceso máximo de Kotlin |
---|---|---|
public |
global | global |
protected |
mismo paquete y todos los subpaquetes | todas las subclases |
internal |
no apoyo | mismo modulo |
default ,por defecto |
mismo paquete | global |
private |
amable | amable |
transferencia de derechos de acceso
Al heredar derechos de acceso, el alcance de acceso del modificador de acceso no se puede ampliar, pero puede ser más estricto (reduciendo el alcance de acceso).
formación
crear
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]]
}
método común
map
Vuelva a colocar cada elemento después de la operación.
val arr = intArrayOf(1, 2, 3)
arr.map {
it * 2
}.also {
println(it) //[2, 4, 6]
}
flatMap
Coloque cada elemento en un conjunto después de la operación, y luego integre todos los elementos en este conjunto cuando opere el siguiente elemento, y finalmente devuelva el conjunto integrado.
val arr = intArrayOf(1, 2, 3)
arr.flatMap {
listOf(it * 2, 0)
}.also {
println(it) //[2, 0, 4, 0, 6, 0]
}
fold
Establezca el valor inicial y luego realice la operación de acumulación.
val arr = intArrayOf(1, 2, 3)
//初始值1,累加
arr.fold(1) {
sum, i ->
sum + i
}.also {
println(it) //7
}
associate
Convierta los elementos de la matriz en pares clave-valor.Si las claves son las mismas, no se almacenarán.
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)
Almacene los elementos de la matriz de acuerdo con una clave específica, y si la clave es la misma, no se almacenará.
val arr = intArrayOf(1, 2, 3)
arr.associateBy {
it * 2
}.also {
println(it) //{2=1, 4=2, 6=3}
}
distinct
deduplicación No se almacena si ya existe.
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 |
---|---|
Cualquier | Objeto |
Unidad | Vacío |
Nada | 没有对应的类型,编译后为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
关键字,成为挂起函数。
El proceso en la función de suspensión es principalmente un cálculo que consume mucho tiempo o operaciones de E/S. Estas operaciones no son adecuadas para que las complete el subproceso principal y necesitan cambiar los subprocesos para ejecutar (la esencia de la suspensión), y luego volver a cambiar después de la ejecución (suspensión ) recuperar).
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
}
La función de suspensión debe llamarse en una rutina, o en otra función de suspensión, de modo que cuando se ejecute la función de suspensión, el subproceso se pueda volver a cambiar automáticamente. Si se llama en un hilo normal, no volverá después de la ejecución (el hilo suspendido no se puede reanudar).
sin bloqueo
Todos los códigos son de naturaleza bloqueante. La instrucción de ensamblaje más básica toma tiempo para ejecutarse y no puede ser interferida por otras instrucciones de ensamblaje durante la ejecución, pero la velocidad es tan rápida que no se puede percibir a nivel de nanosegundos. El código se considera sin bloqueo. Si puede sentirse atascado cuando conoce a alguien, entonces se considera que es causado por el código de bloqueo.
Con las ventajas de la sintaxis de Kotlin, no es un bloqueo escribir procesos que parecen ser síncronos pero asíncronos, todos en el mismo bloque de código y no "bloquearán" el hilo actual. Siempre que el código no esté atascado en un proceso que consume mucho tiempo, cree un subproceso y transfiera el proceso que consume mucho tiempo a otro subproceso, el subproceso actual no bloqueará.
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等复杂运算,其他调度器的任务")
}
}
}
Escriba el código de diferentes hilos en el mismo archivo fuente, evitando una gran cantidad de procesos anidados de devolución de llamada, e incluso el orden de las llamadas.
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("任务启动,等待结果")
}
Escenario de uso de rutina: cuando es necesario designar un hilo específico para ejecutar un proceso que consume mucho tiempo.
fun main() = runBlocking {
launch(Dispatchers.Main) {
//withContext:切换线程执行,然后自动切回原先的线程
val data = withContext(Dispatchers.IO) {
TODO("读取数据")
}
TODO("展示数据")
}
}
suspend
Cuando es necesario cambiar los subprocesos para la ejecución, si esta parte del código se encapsula en una función, se suspend
debe agregar una palabra clave para convertirse en una función de suspensión.
El proceso en la función de suspensión es principalmente un cálculo que consume mucho tiempo o operaciones de E/S. Estas operaciones no son adecuadas para que las complete el subproceso principal y necesitan cambiar los subprocesos para ejecutar (la esencia de la suspensión), y luego volver a cambiar después de la ejecución (suspensión ) recuperar).
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
}
La función de suspensión debe llamarse en una rutina, o en otra función de suspensión, de modo que cuando se ejecute la función de suspensión, el subproceso se pueda volver a cambiar automáticamente. Si se llama en un hilo normal, no volverá después de la ejecución (el hilo suspendido no se puede reanudar).
Si no hay una operación de cambio de subproceso () en la función de suspensión withContext
, esta función solo se puede llamar en la rutina ( suspend
se debe agregar la palabra clave).
Sincronizar
@Volatile
var count: Int = 0
@Synchronized
fun count() {
synchronized(count) {
count++
}
}