Kotlin入门教程

一、Kotlin的主函数

1.1 IDEA文件展开

IDEA项目结构展开

1.2 Kotlin的执行流程

Kotlin的源代码先通过JDK编译成Java字节码,然后再由JVM(Java虚拟机)执行转换成机器码,最终由计算机执行。

执行流程图:

1
Kotlin源代码(.kt) → Java字节码(.class) → JVM虚拟机 → 机器码 → 计算机执行

1.3 第一个Kotlin程序

在Kotlin中,程序的入口点是main函数。下面是一个最简单的Kotlin程序:

1
2
3
fun main(args: Array<String>) {
println("Hello, World!")
}

代码解析:

  • fun:关键字,用于声明函数
  • main:函数名,程序入口点
  • args: Array<String>:参数,是一个字符串数组
  • println():打印输出函数,输出内容后自动换行

二、变量与基础数据类型

2.1 变量声明

在Kotlin中,变量可以使用valvar关键字来声明:

关键字含义是否可变使用场景
valvalue(值)不可变声明后不可重新赋值,类似Java的final
varvariable(变量)可变声明后可以重新赋值
1
2
3
4
5
6
7
8
val a: Int = 10  // 不可变变量,声明后不能重新赋值
var b: Int = 20 // 可变变量,声明后可以重新赋值

// 错误示例:val声明的变量不能重新赋值
// a = 30 // 编译错误

// 正确示例:var声明的变量可以重新赋值
b = 30 // 正确

最佳实践: 优先使用val,只有在确实需要修改变量值时才使用var,这样可以减少程序中的可变状态,降低出错风险。

2.2 基础数据类型

Kotlin提供了以下基础数据类型:

整数类型

类型位数范围最大值常量最小值常量
Byte8位-128 到 127Byte.MAX_VALUE = 127Byte.MIN_VALUE = -128
Short16位-32768 到 32767Short.MAX_VALUE = 32767Short.MIN_VALUE = -32768
Int32位-2147483648 到 2147483647Int.MAX_VALUE = 2147483647Int.MIN_VALUE = -2147483648
Long64位-9223372036854775808 到 9223372036854775807Long.MAX_VALUE = 9223372036854775807Long.MIN_VALUE = -9223372036854775808

浮点类型

类型位数说明最大值最小值
Float32位单精度浮点数3.4028235e381.4e-45
Double64位双精度浮点数1.7976931348623157e3084.9e-324

其他类型

类型位数说明范围/取值
Char16位Unicode字符‘\u0000’ 到 ‘\uFFFF’
Boolean1位布尔值true 或 false
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 整数类型示例
val byteNum: Byte = 127
val shortNum: Short = 32767
val intNum: Int = 2147483647
val longNum: Long = 9223372036854775807

// 浮点类型示例
val floatNum: Float = 3.14f // 注意:Float类型需要加f或F后缀
val doubleNum: Double = 3.141592653589793

// 字符类型示例
val charA: Char = 'A'
val charChinese: Char = '中'

// 布尔类型示例
val isTrue: Boolean = true
val isFalse: Boolean = false

2.3 空值类型

Kotlin的一个重要特性是空安全(Null Safety)。在Kotlin中,空值类型用null表示,它表示一个不存在的值。

可空类型声明

1
2
// 在类型后面加?表示该变量可以为null
var text: String? = null

空值检查

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 方式一:显式检查
fun main(args: Array<String>) {
val text: String? = null
if (text != null) {
println(text.length)
} else {
println("text is null")
}
}

// 方式二:使用安全调用操作符 ?.
fun main(args: Array<String>) {
val text: String? = null
println(text?.length) // 如果text为null,返回null;否则返回text.length
}

Elvis操作符(?:)

Elvis操作符用于提供默认值,当左侧表达式为null时,使用右侧的值。

1
2
3
4
val text: String? = null
val text2: String? = text ?: "default"
// 如果text不为null,那么text2就等于text,否则就等于"default"
println(text2) // 输出:default

三、函数

3.1 函数基础

在Kotlin中,函数使用fun关键字来声明。函数可以有参数也可以没有参数,可以有返回值也可以没有返回值。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// 基本函数语法
fun functionName(parameter1: Type1, parameter2: Type2): ReturnType {
// 函数体
return result
}

// 示例:求和函数
fun sum(a: Int, b: Int): Int {
return a + b
}

// 简化写法(单表达式函数)
fun sum(a: Int, b: Int) = a + b

// 无返回值函数
fun printHello() {
println("Hello!")
}

3.2 参数默认值

Kotlin支持为函数参数指定默认值,调用时可以省略有默认值的参数。

1
2
3
4
5
6
7
8
9
fun sum(a: Int, b: Int = 0): Int {
return a + b
}

// 调用示例
fun main() {
println(sum(10)) // 输出:10(b使用默认值0)
println(sum(10, 5)) // 输出:15
}

3.3 可变参数(vararg)

在Kotlin中,函数可以使用vararg关键字来声明可变数量的参数。

1
2
3
4
5
6
7
8
9
10
11
fun main(args: Array<String>) {
println(sum(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 556, 48))
}

fun sum(vararg nums: Int): Int {
var result = 0
for (num in nums) {
result += num
}
return result
}

注意事项:

  • 一个函数最多只能有一个vararg参数
  • vararg参数通常是函数的最后一个参数
  • 如果vararg参数不是最后一个,其后的参数需要使用命名参数传递

四、循环

4.1 for循环

在Kotlin中,for循环可以遍历任何提供迭代器的对象。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
fun main(args: Array<String>) {
// 1. 使用 until(不包含结束值)
println("使用 until:")
for (i in 0 until 10) {
println(i) // 输出:0 到 9
}

// 2. 使用 ..(包含结束值,闭区间)
println("使用 ..:")
for (i in 0..9) {
println(i) // 输出:0 到 9
}

// 3. 使用 step(指定步长)
println("使用 step:")
for (i in 0 until 10 step 2) {
println(i) // 输出:0, 2, 4, 6, 8
}

// 4. 使用 downTo(递减)
println("使用 downTo:")
for (i in 9 downTo 0) {
println(i) // 输出:9 到 0
}

// 5. 遍历数组/集合
val arr = arrayOf(1, 2, 3, 4, 5)
for (item in arr) {
println(item)
}

// 6. 带索引遍历
for ((index, value) in arr.withIndex()) {
println("索引:$index,值:$value")
}
}

4.2 while循环

1
2
3
4
5
6
7
fun main(args: Array<String>) {
var i = 0
while (i < 10) {
println(i)
i++
}
}

4.3 循环控制

在Kotlin中,可以使用以下关键字控制循环:

关键字作用说明
break结束循环立即终止当前循环
continue跳过当前迭代跳过本次循环,继续下一次迭代
break@label跳出指定循环跳出标签标记的外部循环
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
fun main() {
// break示例
for (i in 1..10) {
if (i == 5) break
println(i) // 输出:1, 2, 3, 4
}

// continue示例
for (i in 1..10) {
if (i == 5) continue
println(i) // 输出:1, 2, 3, 4, 6, 7, 8, 9, 10
}

// 标签跳出示例
outer@ for (i in 1..3) {
for (j in 1..3) {
if (i == 2 && j == 2) break@outer
println("i=$i, j=$j")
}
}
}

五、数组与类型判断

5.1 数组创建

在Kotlin中,数组可以使用arrayOf函数来创建。

1
2
3
4
5
6
7
8
9
10
11
12
13
fun main(args: Array<String>) {
// 创建整数数组
val intArr = arrayOf(1, 2, 3, 4, 5)

// 创建字符串数组
val strArr = arrayOf("Hello", "World", "Kotlin")

// 创建混合类型数组
val mixedArr = arrayOf(1, "Hello", 3.14, true)

// 创建指定大小的数组
val sizedArr = Array(5) { it * 2 } // [0, 2, 4, 6, 8]
}

5.2 类型判断(is关键字)

使用is关键字可以判断一个对象是否是某种类型。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
fun main(args: Array<String>) {
val arr = arrayOf(1, 2, 3, 4, 5)

// 类型判断
if (arr is Array<Int>) {
println("arr is Array<Int>")
}

// 智能类型转换
val obj: Any = "Hello"
if (obj is String) {
// 在此分支中,obj自动被识别为String类型
println(obj.length) // 无需强制转换
}
}

IDEA快捷键: Ctrl+; 可以添加类型显示,让代码更清晰。


六、类与对象

6.1 类的基本定义

在Kotlin中,类使用class关键字来声明。类可以有属性和方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 定义一个简单的类
class Person {
var name: String = ""
var age: Int = 0

fun sayHello() {
println("Hello, my name is $name, I am $age years old.")
}
}

// 使用类
fun main(args: Array<String>) {
val person = Person()
person.name = "张三"
person.age = 18
person.sayHello()
}

6.2 构造函数

主构造函数

主构造函数是类头部的一部分,紧跟在类名之后。

1
2
3
4
5
6
7
8
9
10
11
12
13
class Student(name: String, var age: Int = 0) {
var name: String = name.trim() // 去掉字符串首尾的空格

fun sayHello() {
println("Hello, my name is $name, I am $age years old. I am a student.")
}
}

// 使用
fun main() {
val student = Student("张三", 18)
student.sayHello()
}

初始化块(init)

初始化块用init关键字声明,属于类的一部分。每创建一个类的实例,所有初始化块都会执行。

重要: Kotlin次构造函数必须先调用主构造函数,因此init块一定会在所有次构造函数逻辑前执行。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class Student(name: String, var age: Int = 0) {
var name: String = name.trim()

init {
// lowercase().startsWith("a")先把字符串转为小写,再判断是否以指定前缀开头
if (name.lowercase().startsWith("a")) {
this.name = name
} else {
this.name = "User"
println("Name does not start with A, using 'User' as default.")
}
}

fun sayHello() {
println("Hello, my name is $name, I am $age years old. I am a student.")
}
}

次构造函数(constructor)

次构造函数用constructor关键字声明,核心规则:

  1. 一个类可以有多个次构造函数(重载)
  2. 所有次构造函数必须直接或间接调用主构造函数(通过this()
  3. 次构造函数的逻辑执行在init块之后
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
class Student(name: String, var age: Int = 0) {
var name: String = name.trim()

init {
if (name.lowercase().startsWith("a")) {
this.name = name
} else {
this.name = "User"
println("Name does not start with A, using 'User' as default.")
}
}

// 次构造函数:只传入name,age使用默认值
constructor(name: String) : this(name, 0) {
// 次构造函数的逻辑在init块之后执行
println("Secondary constructor called")
}

fun sayHello() {
println("Hello, my name is $name, I am $age years old. I am a student.")
}
}

6.3 Getter和Setter

在Kotlin中,类的属性可以使用varval关键字来声明:

  • var关键字声明的属性可以读写
  • val关键字声明的属性只能读

Kotlin会自动为属性生成get()set()方法,也可以自定义。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class Student(name: String, var age: Int = 0) {
var name: String = name.trim()
get() {
// 自定义getter
return field // field是属性的幕后字段
}
set(value) {
// 自定义setter
field = value
}
}

// 使用示例
fun main() {
val student = Student("张三", 18)
println(student.name) // 调用getter
student.name = "李四" // 调用setter
}

说明: field是Kotlin中专门用于访问属性「底层存储字段」的关键字,避免在getter/setter中造成递归调用。

6.4 lateinit延迟初始化

lateinit(延迟初始化)是Kotlin中用于标记可变属性(var)的关键字,允许属性在声明时不初始化,而是推迟到后续代码中初始化。

语法: lateinit var 变量名: 类型

使用限制:

  • 仅支持var,不支持val
  • 不支持基本数据类型(Int、Long、Double等)
  • 必须在使用前初始化,否则会抛出异常
1
2
3
4
5
6
7
8
9
10
11
12
13
14
class MainActivity {
lateinit var adapter: RecyclerView.Adapter<*>

fun initAdapter() {
adapter = MyAdapter()
}

fun useAdapter() {
if (::adapter.isInitialized) {
// 检查是否已初始化
adapter.notifyDataSetChanged()
}
}
}

6.5 伴随对象(companion object)

伴随对象是Kotlin中替代Java静态成员(static)的核心语法,本质是「隶属于类的单例对象」,可以包含属性、方法、常量等。

作用: 实现类级别的操作,无需创建类实例就能调用。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class MyClass {
companion object {
const val MAX_COUNT = 100

fun create(): MyClass {
return MyClass()
}
}
}

// 使用
fun main() {
println(MyClass.MAX_COUNT)
val instance = MyClass.create()
}

6.6 单例模式(object关键字)

Kotlin中的object关键字是实现饿汉式单例的简洁方式,编译器会自动生成线程安全的单例对象。

语法: object 单例名 { ... }

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
object DatabaseHelper {
private val connection: Connection? = null

fun connect() {
// 连接数据库
}

fun disconnect() {
// 断开连接
}
}

// 使用
fun main() {
DatabaseHelper.connect()
DatabaseHelper.disconnect()
}

单例模式的优势:

  1. 简单易用:无需手动编写复杂的代码,直接使用object关键字即可
  2. 线程安全:Kotlin会自动处理多线程环境下的线程安全问题
  3. 延迟初始化:单例对象在第一次被访问时才会初始化
  4. 全局唯一:单例对象在整个应用程序生命周期内只有一个实例

6.7 by lazy延迟初始化

by lazy是Kotlin中实现延迟初始化的一种方式,将属性的初始化延迟到第一次访问时。

语法: val 变量名: 类型 by lazy { 初始化表达式 }

注意: 仅支持val,不支持var

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
class MyClass {
// 使用 by lazy:第一次调用才初始化,后续直接用缓存值
val userNameByLazy: String by lazy {
println("执行初始化逻辑")
"张三"
}

// 不使用 by lazy:程序启动就初始化
val userNameNoLazy: String = run {
println("立即执行初始化逻辑")
"张三"
}
}

// 使用
fun main() {
val myClass = MyClass()
println("对象创建完成")
println(myClass.userNameByLazy) // 此时才执行初始化
println(myClass.userNameByLazy) // 使用缓存值,不再初始化
}

by lazy的优势:

  1. 延迟初始化:避免过早初始化导致的资源浪费
  2. 线程安全:默认情况下,Kotlin会处理多线程环境下的线程安全问题
  3. 全局唯一:属性在整个应用程序生命周期内只有一个实例

6.8 枚举类(enum class)

枚举类是Kotlin中一种特殊的类,用于表示固定数量的枚举值(如方向、状态、颜色等)。

语法: enum class 枚举名 { 枚举值1, 枚举值2, ... }

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
// 简单枚举
enum class Direction {
NORTH, SOUTH, EAST, WEST
}

// 带属性的枚举
enum class Color(val rgb: Int) {
RED(0xFF0000),
GREEN(0x00FF00),
BLUE(0x0000FF)
}

// 使用
fun main() {
val direction = Direction.NORTH
println(direction) // 输出:NORTH

// 遍历枚举值
for (dir in Direction.values()) {
println(dir)
}

// 获取枚举值
val color = Color.RED
println(color.rgb) // 输出:16711680
}

枚举类的优势:

  1. 类型安全:枚举类的每个值都是枚举类的一个实例
  2. 可遍历:枚举类的所有值都可以通过values()方法遍历
  3. 可比较:枚举类的每个值都可以进行比较操作
  4. 可序列化:枚举类的每个值都可以进行序列化操作
  5. 可用于when语句:枚举类的每个值都可以直接用于when语句

6.9 when表达式

when是Kotlin中替代Java switch语句的增强版分支结构,功能更强大、语法更简洁。

语法:

1
2
3
4
5
6
when (表达式) {
匹配值1 -> 代码块1
匹配值2 -> 代码块2
...
else -> 代码块N
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
fun main() {
// 值匹配
val x = 5
when (x) {
1 -> println("x is 1")
2 -> println("x is 2")
in 3..10 -> println("x is between 3 and 10")
else -> println("x is unknown")
}

// 类型匹配(使用is关键字)
val obj: Any = "Hello"
when (obj) {
is Int -> println("obj is Int")
is String -> println("obj is String, length = ${obj.length}")
is Double -> println("obj is Double")
else -> println("obj is unknown type")
}

// 作为表达式使用
val result = when (x) {
1 -> "one"
2 -> "two"
else -> "other"
}
}

when表达式的优势:

  1. 功能更强大:支持值匹配、类型匹配、表达式返回值等多种场景
  2. 语法更简洁:比switch语句更简洁,代码更易读
  3. 类型安全:匹配值必须是枚举值或常量,避免类型转换错误
  4. 可扩展性:可以很方便地扩展新的匹配值

6.10 内部类(inner class)

inner内部类是Kotlin中一种特殊的内部类,它可以访问外部类的成员(包括属性、方法、构造函数等)。

语法: inner class 内部类名(参数1: 类型1, 参数2: 类型2, ...) { ... }

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class Outer {
private val outerProperty = "Outer Property"

inner class Inner {
fun accessOuter() {
// 内部类可以直接访问外部类的成员
println(outerProperty)
}
}
}

// 使用
fun main() {
val outer = Outer()
val inner = outer.Inner() // 创建内部类实例需要外部类实例
inner.accessOuter()
}

inner内部类的优势:

  1. 访问外部类成员:可以直接访问外部类的属性、方法、构造函数等
  2. 灵活实例化:可以被外部类实例化,实现更灵活的代码组织
  3. 避免命名冲突:内部类的成员可以与外部类的成员重名

6.11 可变列表(mutableListOf)

mutableListOf<T>是Kotlin中一种可变列表(MutableList),可以存储任意类型的元素,并且可以动态地添加、删除、修改元素。

语法: mutableListOf<T>(元素1, 元素2, ...)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
fun main() {
// 创建可变列表
val names = mutableListOf("张三", "李四", "王五")

// 添加元素
names.add("赵六")

// 删除元素
names.remove("李四")

// 修改元素
names[0] = "张三丰"

// 遍历列表
for (name in names) {
println(name)
}
}

mutableListOf的优势:

  1. 动态操作:可以根据需要动态地添加、删除、修改元素
  2. 类型灵活:可以存储任意类型的元素
  3. 遍历方便:可以使用for循环、forEach方法等方便地遍历

七、继承、接口与抽象类

7.1 open关键字

在Kotlin中,类和方法默认是final的,不能被继承或重写。使用open关键字可以修饰类、属性、方法,表明它们可以被继承、重写或实现。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 可被继承的类
open class Animal(val name: String) {
// 可被重写的方法
open fun makeSound() {
println("$name makes a sound")
}
}

// 继承类
class Dog(name: String) : Animal(name) {
override fun makeSound() {
println("$name barks")
}
}

7.2 sealed关键字(密封类)

sealed关键字用于修饰类,表明它们只能被同一文件中的子类继承,不能被外部类直接实例化。

作用: 限制类的继承层次,配合when表达式使用时可以确保覆盖所有情况。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
sealed class Result {
data class Success(val data: String) : Result()
data class Error(val message: String) : Result()
object Loading : Result()
}

fun handleResult(result: Result) {
when (result) {
is Result.Success -> println("成功:${result.data}")
is Result.Error -> println("错误:${result.message}")
Result.Loading -> println("加载中...")
// 不需要else分支,因为已经覆盖所有情况
}
}

7.3 abstract关键字(抽象类)

abstract关键字用于修饰类、属性、方法,表明它们必须被继承、重写或实现。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
abstract class Shape {
abstract val area: Double // 抽象属性

abstract fun draw() // 抽象方法

fun display() { // 具体方法
println("Displaying shape")
}
}

class Circle(val radius: Double) : Shape() {
override val area: Double
get() = Math.PI * radius * radius

override fun draw() {
println("Drawing circle with radius $radius")
}
}

7.4 data关键字(数据类)

data关键字用于修饰类,表明它是一个数据类,用于存储数据。编译器会自动生成以下方法:

  • equals() / hashCode()
  • toString()
  • copy()
  • componentN()(用于解构声明)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
data class User(val name: String, val age: Int, val city: String)

fun main() {
val user1 = User("张三", 18, "北京")
val user2 = User("张三", 18, "北京")

println(user1 == user2) // true(自动生成的equals方法)
println(user1) // User(name=张三, age=18, city=北京)

// copy方法
val user3 = user1.copy(age = 20)
println(user3) // User(name=张三, age=20, city=北京)

// 解构声明
val (name, age, city) = user1
println("$name, $age, $city")
}

7.5 接口(interface)

接口使用interface关键字声明,可以定义抽象方法和默认实现。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
interface Clickable {
fun click() // 抽象方法

fun show() { // 默认实现
println("Showing clickable item")
}
}

class Button : Clickable {
override fun click() {
println("Button clicked")
}
}

// 实现多个接口
interface Draggable {
fun drag()
}

class ImageButton : Clickable, Draggable {
override fun click() {
println("Image button clicked")
}

override fun drag() {
println("Dragging image button")
}
}

接口的优势:

  1. 多实现:一个类可以实现多个接口
  2. 解耦代码:接口可以将代码解耦,使代码更灵活、可维护
  3. 定义规范:接口可以定义一组方法规范

7.6 by关键字(委托)

by关键字用于实现接口的委托,将接口的实现委托给另一个对象,避免重复实现接口方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
interface Printer {
fun print(message: String)
}

class ConsolePrinter : Printer {
override fun print(message: String) {
println(message)
}
}

// 使用by关键字委托实现
class LogPrinter(private val printer: Printer) : Printer by printer {
// 只需要重写需要自定义的方法
override fun print(message: String) {
printer.print("[LOG] $message")
}
}

// 使用
fun main() {
val consolePrinter = ConsolePrinter()
val logPrinter = LogPrinter(consolePrinter)
logPrinter.print("Hello")
}

by关键字的常见用法:

1
2
3
4
5
6
7
8
9
// by lazy:延迟初始化
val userName: String by lazy {
println("执行初始化逻辑")
"张三"
}

// by lazy vs 直接赋值的区别:
// by lazy:第一次调用才初始化,后续使用缓存值
// 直接赋值:程序启动就立即初始化

by关键字的优势:

  1. 代码复用:将接口实现委托给另一个对象
  2. 灵活切换:可以动态地切换委托对象
  3. 减少代码量:避免重复实现接口方法

7.7 with关键字

with关键字用于在一个对象的作用域内执行代码,避免重复引用对象。

核心作用: 给一段代码块指定一个上下文对象,在代码块里直接访问这个对象的属性/方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
data class User(var name: String, var age: Int, var city: String)

fun main() {
val user = User("张三", 18, "北京")

// 不使用with:反复写user.xxx,代码冗余
user.name = "李四"
user.age = 20
user.city = "上海"
println("姓名:${user.name},年龄:${user.age},城市:${user.city}")

// 使用with:指定user为上下文,代码块内直接写属性/方法
with(user) {
name = "王五"
age = 22
city = "广州"
println("姓名:$name,年龄:$age,城市:$city")
}
}

with关键字的优势:

  1. 减少代码量:避免重复引用对象
  2. 提高可读性:将代码块内的代码与对象关联起来
  3. 方便调用:可以方便地调用对象的方法

八、集合

8.1 不可变集合

Kotlin区分可变集合和不可变集合,不可变集合只能读取,不能修改。

1
2
3
4
5
6
7
8
// 不可变列表
val list = listOf(1, 2, 3, 4, 5)

// 不可变Set集合(元素唯一、无序)
val set = setOf(1, 2, 3, 2, 1) // 结果:[1, 2, 3]

// 不可变Map
val map = mapOf("a" to 1, "b" to 2, "c" to 3)

Set集合的特点:

  • 元素唯一:不允许重复元素
  • 无序:和List的「有序、可重复」形成核心区别

8.2 可变集合

可变集合可以动态地添加、删除、修改元素。

1
2
3
4
5
6
7
8
9
10
11
12
// 可变列表
val mutableList = mutableListOf(1, 2, 3)
mutableList.add(4)
mutableList.removeAt(0)

// 可变Set
val mutableSet = mutableSetOf(1, 2, 3)
mutableSet.add(4)

// 可变Map
val mutableMap = mutableMapOf("a" to 1)
mutableMap["b"] = 2

8.3 mutableListOf vs arrayListOf

1
2
3
4
5
// mutableListOf:返回MutableList接口(Kotlin标准接口,面向接口编程)
val list1 = mutableListOf(1, 2, 3)

// arrayListOf:返回java.util.ArrayList类(直接暴露Java的ArrayList实现类)
val list2 = arrayListOf(1, 2, 3)

推荐使用: mutableListOf(),因为它面向接口编程,更加灵活。

8.4 集合遍历

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
data class User(val name: String, val age: Int, val city: String)

fun main() {
val users = mutableListOf(
User("张三", 18, "北京"),
User("李四", 20, "上海"),
User("王五", 22, "广州")
)

// 使用for循环遍历
for (user in users) {
println("姓名:${user.name},年龄:${user.age},城市:${user.city}")
}

// 使用with遍历
with(users) {
for (user in this) {
println("姓名:${user.name},年龄:${user.age},城市:${user.city}")
}
}

// 使用forEach
users.forEach { user ->
println("姓名:${user.name},年龄:${user.age},城市:${user.city}")
}
}

8.5 Map集合

Map集合用于存储键值对,每个键都是唯一的。

Map集合的优势

  1. 方便存储键值对:可以方便地存储键值对,实现快速的查找和访问
  2. 方便遍历:可以方便地遍历键值对,实现对集合中元素的操作
  3. 动态管理:可以方便地添加、删除、修改键值对
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
fun main() {
// 创建Map
val map = mapOf("a" to 1, "b" to 2, "c" to 3)

// 访问元素
println(map["a"]) // 输出:1

// 获取第一个键值对
val firstPair = map.entries.first()
println("第一个键值对:$firstPair")

// 遍历Map
for ((key, value) in map) {
println("$key -> $value")
}
}

字符串处理

1
2
3
4
5
6
7
8
9
10
fun main() {
// uppercase():将字符串转换为大写
val str = "hello"
val upperStr = str.uppercase()
println(upperStr) // 输出:HELLO

// lowercase():将字符串转换为小写
val lowerStr = "HELLO".lowercase()
println(lowerStr) // 输出:hello
}

Map集合示意图
数组示意图

8.6 数组常用操作

Kotlin为数组提供了丰富的操作方法。

数组创建

1
2
3
4
5
6
7
8
// 创建IntArray
val intArr = IntArray(5) // 创建长度为5的IntArray,默认值为0

// 创建字符串数组
val strArr = arrayOf("苹果", "香蕉")

// 使用arrayOf创建数组
val arr = arrayOf(10, 20, 30, 40, 400)

常用属性和函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
fun main() {
val arr = arrayOf(10, 20, 30, 40, 400)

// 基本属性
println("长度:${arr.size}") // 输出:5
println("是否为空:${arr.isEmpty()}") // 输出:false
println("包含 20:${arr.contains(20)}") // 输出:true

// 统计函数
println("最大值:${arr.maxOrNull()}") // 输出:400
println("最小值:${arr.minOrNull()}") // 输出:10
println("求和:${arr.sum()}") // 输出:500
println("平均值:${arr.average()}") // 输出:100.0
println("元素个数:${arr.count()}") // 输出:5
}

8.7 集合过滤操作

Kotlin提供了强大的过滤功能,可以根据条件筛选集合元素。

filter系列方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
fun main() {
val arr = arrayOf(10, 20, 30, 40, 400)

// filter:过滤满足条件的元素
println("过滤大于 30 的元素:${arr.filter { it > 30 }}")
// 输出:[40, 400]

// filterIndexed:带索引的过滤
println("过滤索引为偶数的元素:${arr.filterIndexed { index, _ -> index % 2 == 0 }}")
// 输出:[10, 30, 400]

// filterNot:过滤不满足条件的元素
println("过滤不是偶数的元素:${arr.filterNot { it % 2 == 0 }}")
// 输出:[30]

// filterIsInstance:过滤指定类型的元素
val mixedArr = arrayOf(1, "Hello", 3.14, true, 5)
println("过滤Int类型元素:${mixedArr.filterIsInstance<Int>()}")
// 输出:[1, 5]
}

map映射操作

1
2
3
4
5
6
7
8
9
10
11
fun main() {
val arr = arrayOf(10, 20, 30, 40, 400)

// map:将每个元素映射为新值
println("映射每个元素为其平方:${arr.map { it * it }}")
// 输出:[100, 400, 900, 1600, 160000]

// mapIndexed:带索引的映射
println("映射为索引和值的组合:${arr.mapIndexed { index, value -> "[$index]=$value" }}")
// 输出:[[0]=10, [1]=20, [2]=30, [3]=40, [4]=400]
}

8.8 集合分区与分组

partition方法

partition()方法用于将列表根据指定条件分为两个列表。

1
2
3
4
5
6
7
8
fun main() {
val arr = arrayOf(10, 20, 30, 40, 400)

// 将数组分为偶数和奇数两部分
val (even, odd) = arr.partition { it % 2 == 0 }
println("偶数列表:$even") // 输出:[10, 20, 40, 400]
println("奇数列表:$odd") // 输出:[30]
}

groupBy方法

groupBy()方法用于将列表根据指定条件分组。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
fun main() {
val numbers = listOf(10, 20, 30, 40, 400)

// 按奇偶性分组
val groupedMap = numbers.groupBy { it % 2 == 0 }
println(groupedMap)
// 输出:{true=[10, 20, 40, 400], false=[30]}

// 按字符串长度分组
val words = listOf("apple", "bat", "cat", "dog", "elephant")
val byLength = words.groupBy { it.length }
println(byLength)
// 输出:{5=[apple], 3=[bat, cat, dog], 8=[elephant]}
}

8.9 集合条件判断

anyallnone是Kotlin集合中判断元素是否满足条件的核心函数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
fun main() {
val numbers = listOf(10, 20, 30, 40, 400)

// any:是否存在满足条件的元素
val hasEndWith0 = numbers.any { it.toString().endsWith("0") }
println("是否有以0结尾的数字:$hasEndWith0") // 输出:true

// all:是否所有元素都满足条件
val allPositive = numbers.all { it > 0 }
println("是否所有元素都大于0:$allPositive") // 输出:true

// none:是否没有元素满足条件
val noneGreaterThan30 = numbers.none { it > 30 }
println("是否没有元素大于30:$noneGreaterThan30") // 输出:false
}

8.10 集合连接与展开

joinToString方法

joinToString()方法用于将集合元素连接成字符串。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
fun main() {
val arr = arrayOf(10, 20, 30, 40, 400)

println("连接每个元素:${arr.joinToString()}")
// 输出:10, 20, 30, 40, 400

println("连接每个元素为逗号分隔:${arr.joinToString(",")}")
// 输出:10,20,30,40,400

println("连接每个元素为空格分隔:${arr.joinToString(" ")}")
// 输出:10 20 30 40 400

// 自定义前缀、后缀和限制
println(arr.joinToString(", ", "[", "]") { "num=$it" })
// 输出:[num=10, num=20, num=30, num=40, num=400]
}

flatten方法

flatten()方法用于将嵌套的列表展开为一个列表。

1
2
3
4
5
fun main() {
val nestedList = listOf(listOf(1, 2, 3), listOf(4, 5, 6))
val flattenedList = nestedList.flatten()
println(flattenedList) // 输出:[1, 2, 3, 4, 5, 6]
}

8.11 集合切片与截取

slice方法

slice()方法用于获取列表中指定索引范围内的元素。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
fun main() {
val arr = arrayOf(10, 20, 30, 40, 400)

// 获取指定索引范围的元素
println("索引1到3的元素:${arr.slice(1..3)}")
// 输出:[20, 30, 40]

// 使用step指定步长
println("索引1到3,步长为2:${arr.slice(1..3 step 2)}")
// 输出:[20, 40]

// 使用指定索引集合
println("指定索引的元素:${arr.slice(listOf(0, 2, 4))}")
// 输出:[10, 30, 400]
}

take和drop系列方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
fun main() {
val arr = arrayOf(10, 20, 30, 40, 400)

// take:获取前N个元素
val firstThree = arr.take(3)
println("前3个元素:$firstThree") // 输出:[10, 20, 30]

// takeLast:获取后N个元素
val lastThree = arr.takeLast(3)
println("后3个元素:$lastThree") // 输出:[30, 40, 400]

// drop:丢弃前N个元素
val droppedList = arr.drop(2)
println("丢弃前2个元素:$droppedList") // 输出:[30, 40, 400]

// dropLast:丢弃后N个元素
val droppedLastList = arr.dropLast(2)
println("丢弃后2个元素:$droppedLastList") // 输出:[10, 20, 30]
}

takeWhile和dropWhile方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
fun main() {
val arr = arrayOf(10, 20, 30, 40, 400)

// takeWhile:获取满足条件的前N个元素(遇到不满足条件的停止)
val takenList = arr.takeWhile { it < 30 }
println("获取小于30的元素:$takenList") // 输出:[10, 20]

// dropWhile:丢弃满足条件的前N个元素(遇到不满足条件的停止)
val droppedList = arr.dropWhile { it < 30 }
println("丢弃小于30的元素:$droppedList") // 输出:[30, 40, 400]

// takeLastWhile:获取满足条件的后N个元素
val takenLastList = arr.takeLastWhile { it > 30 }
println("获取大于30的后N个元素:$takenLastList") // 输出:[40, 400]

// dropLastWhile:丢弃满足条件的后N个元素
val droppedLastList = arr.dropLastWhile { it > 30 }
println("丢弃大于30的后N个元素:$droppedLastList") // 输出:[10, 20, 30]
}

8.12 集合转换与分块

toList方法

toList()方法用于将范围或其他集合转换为列表。

1
2
3
4
5
fun main() {
// 将范围转换为列表
val list = (10..20).toList()
println(list) // 输出:[10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20]
}

chunked方法

chunked()方法用于将列表分为指定大小的子列表。

1
2
3
4
5
6
7
8
9
10
11
fun main() {
val list = (1..10).toList()

// 分块为大小为3的子列表
val chunkedList = list.chunked(3)
println(chunkedList) // 输出:[[1, 2, 3], [4, 5, 6], [7, 8, 9], [10]]

// 分块并对每个子列表进行操作
val sumList = list.chunked(3) { it.sum() }
println(sumList) // 输出:[6, 15, 24, 10]
}

windowed方法

windowed()方法用于将列表分为指定大小的滑动窗口。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
fun main() {
val list = (1..10).toList()

// 创建大小为3的滑动窗口
val windowedList = list.windowed(3)
println(windowedList)
// 输出:[[1, 2, 3], [2, 3, 4], [3, 4, 5], [4, 5, 6], [5, 6, 7], [6, 7, 8], [7, 8, 9], [8, 9, 10]]

// 创建窗口并对每个窗口进行操作
val sumWindow = list.windowed(3) { it.sum() }
println(sumWindow) // 输出:[6, 9, 12, 15, 18, 21, 24, 27]

// 指定步长
val steppedWindow = list.windowed(3, step = 2)
println(steppedWindow)
// 输出:[[1, 2, 3], [3, 4, 5], [5, 6, 7], [7, 8, 9], [9, 10]]
}

8.13 集合元素获取

elementAt系列方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
fun main() {
val list = listOf(10, 20, 30, 40, 400)

// elementAt:获取指定索引的元素(索引越界会抛出异常)
val thirdElement = list.elementAt(2)
println("索引2的元素:$thirdElement") // 输出:30

// elementAtOrNull:获取指定索引的元素,索引越界返回null
val fifthElement = list.elementAtOrNull(4)
println("索引4的元素:$fifthElement") // 输出:400

val outOfBound = list.elementAtOrNull(10)
println("索引10的元素:$outOfBound") // 输出:null

// elementAtOrElse:获取指定索引的元素,索引越界返回默认值
val seventhElement = list.elementAtOrElse(6) { -1 }
println("索引6的元素:$seventhElement") // 输出:-1
}

random方法

random()方法用于获取集合中的随机元素。

1
2
3
4
5
6
7
8
9
10
11
fun main() {
val list = listOf(10, 20, 30, 40, 400)

// 获取随机元素
val randomElement = list.random()
println("随机元素:$randomElement")

// 使用随机种子
val randomWithSeed = list.random(java.util.Random(42))
println("带种子的随机元素:$randomWithSeed")
}

8.14 集合统计与聚合

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
fun main() {
val list = listOf(10, 20, 30, 40, 400)

// isEmpty:判断列表是否为空
val isEmpty = list.isEmpty()
println("是否为空:$isEmpty") // 输出:false

// sum:计算总和
val sum = list.sum()
println("总和:$sum") // 输出:500

// count:计算元素数量(可带条件)
val count = list.count { it > 100 }
println("大于100的元素数量:$count") // 输出:1

// average:计算平均值
val average = list.average()
println("平均值:$average") // 输出:100.0

// maxOrNull:获取最大值(空集合返回null)
val max = list.maxOrNull()
println("最大值:$max") // 输出:400

// minOrNull:获取最小值(空集合返回null)
val min = list.minOrNull()
println("最小值:$min") // 输出:10

// sumOf:自定义聚合计算
val sumOf = list.sumOf { it.toDouble() }
println("sumOf结果:$sumOf") // 输出:500.0
}

8.15 集合排序

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
fun main() {
val list = listOf(400, 10, 30, 40, 20)

// sorted:升序排序
val sortedList = list.sorted()
println("升序排序:$sortedList") // 输出:[10, 20, 30, 40, 400]

// sortedDescending:降序排序
val sortedDescendingList = list.sortedDescending()
println("降序排序:$sortedDescendingList") // 输出:[400, 40, 30, 20, 10]

// sortedBy:根据指定函数排序
val sortedByList = list.sortedBy { it % 10 }
println("按个位数排序:$sortedByList") // 输出:[10, 20, 30, 400, 40]

// sortedByDescending:根据指定函数降序排序
val sortedByDescList = list.sortedByDescending { it % 10 }
println("按个位数降序:$sortedByDescList") // 输出:[400, 40, 30, 20, 10]

// sortedWith:使用比较器排序
val sortedWithList = list.sortedWith(compareBy { it % 100 })
println("使用比较器排序:$sortedWithList") // 输出:[400, 10, 20, 30, 40]

// 自定义比较器
val customSorted = list.sortedWith { a, b ->
when {
a % 10 < b % 10 -> -1
a % 10 > b % 10 -> 1
else -> 0
}
}
println("自定义排序:$customSorted")
}

8.16 集合操作总结表

操作类型方法说明
过滤filter过滤满足条件的元素
filterIndexed带索引的过滤
filterNot过滤不满足条件的元素
filterIsInstance过滤指定类型的元素
映射map将元素映射为新值
mapIndexed带索引的映射
分区partition分为两个列表
groupBy按条件分组
判断any是否存在满足条件的元素
all是否所有元素都满足条件
none是否没有元素满足条件
截取take获取前N个元素
takeLast获取后N个元素
drop丢弃前N个元素
dropLast丢弃后N个元素
排序sorted升序排序
sortedDescending降序排序
sortedBy按指定函数排序
统计sum求和
average平均值
maxOrNull最大值
minOrNull最小值
count计数

8.17 集合高级排序

compareByDescending方法

1
2
3
4
5
6
7
fun main() {
val list = listOf(10, 20, 30, 40, 400)

// 使用compareByDescending进行降序排序
val sortedWithList = list.sortedWith(compareByDescending { it % 10 })
println(sortedWithList) // 输出:[400, 40, 30, 20, 10]
}

thenBy方法

thenBy()方法用于对已排序的列表进行二级排序(secondary排序)。

1
2
3
4
5
6
7
8
9
10
11
fun main() {
val list = listOf(10, 20, 30, 40, 400)

// 先按个位数排序,个位数相同的再按原值排序
val thenByList = list.sortedWith(compareBy<Int> { it % 10 }.thenBy { it })
println(thenByList) // 输出:[10, 20, 30, 40, 400]

// 先按个位数降序,个位数相同的再按原值升序
val thenByDescList = list.sortedWith(compareByDescending<Int> { it % 10 }.thenBy { it })
println(thenByDescList) // 输出:[400, 40, 30, 20, 10]
}

8.18 集合查找

binarySearch方法

binarySearch()方法用于对已排序的列表进行二分查找,效率比线性查找高很多。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
fun main() {
val list = listOf(10, 20, 30, 40, 400)

// 二分查找(列表必须已排序)
val binarySearch = list.binarySearch(30)
println("30的索引:$binarySearch") // 输出:2

// 查找不存在的元素
val notFound = list.binarySearch(25)
println("25的索引:$notFound") // 输出:-3(负数表示未找到)

// 指定范围查找
val rangeSearch = list.binarySearch(30, 0, 3)
println("范围内查找30:$rangeSearch")
}

注意: 二分查找要求数组必须是有序的!

8.19 Map集合详解

Map集合的创建方式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
fun main() {
// 不可变Map:mapOf()
val immutableMap = mapOf(1 to "张三", 2 to "李四", 3 to "王五")
println(immutableMap)

// 可变Map:mutableMapOf()
val mutableMap = mutableMapOf(1 to "张三", 2 to "李四")
mutableMap[3] = "王五" // 可以添加元素
mutableMap[1] = "赵六" // 可以修改元素
println(mutableMap)

// HashMap:hashMapOf()
val hashMap = hashMapOf(1 to "张三", 2 to "李四")
println(hashMap)
}

mutableMapOf vs hashMapOf

方法返回类型底层实现使用场景
mutableMapOf()MutableMap(接口)Java HashMap99%的Kotlin原生代码
hashMapOf()java.util.HashMap(类)Java HashMap与Java互操作、需要HashMap独有方法

to关键字

to关键字用于创建键值对,将键和值组合成一个Pair对象。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
fun main() {
// 使用to创建键值对
val map = mapOf< Int, String>(1 to "张三", 2 to "李四", 3 to "王五")

// to本质是创建Pair对象
val pair = 1 to "张三" // 等价于 Pair(1, "张三")
println(pair) // 输出:(1, 张三)

// 遍历Map
map.forEach { (key, value) ->
println("键:$key,值:$value")
}

// 使用entries遍历
for ((key, value) in map) {
println("键:$key,值:$value")
}
}

8.20 集合遍历方式总结

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
data class User(val name: String, val age: Int, val city: String)

fun main() {
val users = mutableListOf(
User("张三", 18, "北京"),
User("李四", 20, "上海"),
User("王五", 22, "广州")
)

// 方式1:for循环
for (user in users) {
println("姓名:${user.name},年龄:${user.age},城市:${user.city}")
}

// 方式2:with遍历
with(users) {
for (user in this) {
println("姓名:${user.name},年龄:${user.age},城市:${user.city}")
}
}

// 方式3:forEach遍历
users.forEach {
println("姓名:${it.name},年龄:${it.age},城市:${it.city}")
}

// 方式4:forEach带参数名
users.forEach { user ->
println("姓名:${user.name},年龄:${user.age},城市:${user.city}")
}

// 方式5:forEachIndexed(带索引)
users.forEachIndexed { index, user ->
println("索引:$index,姓名:${user.name}")
}
}

十、类型转换与泛型

10.1 as关键字

as关键字用于将对象转换为指定的类型。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
fun main() {
// 安全的类型转换
val intList = listOf(1, 2, 3)
val anyList: List<Any> = intList
println(anyList) // 输出:[1, 2, 3]

// 不安全的类型转换(可能抛出异常)
val obj: Any = "Hello"
val str: String = obj as String
println(str) // 输出:Hello

// 安全转换操作符 as?
val obj2: Any = 123
val str2: String? = obj2 as? String // 转换失败返回null
println(str2) // 输出:null

// 类型检查
if (obj is String) {
println(obj.length) // 智能类型转换
}
}

10.2 泛型out(协变)

泛型out用于表示只能从泛型类型中读取值,不能写入值。这称为协变(Covariance)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// out T 表示T只能作为返回类型(只能读取)
fun readFromList(list: MutableList<out Number>) {
// 可以读取
val num: Number = list[0]
println(num)

// 不能写入(编译错误)
// list.add(10) // 错误:不能添加元素
}

fun main() {
val intList = mutableListOf(1, 2, 3)
readFromList(intList) // MutableList<Int> 可以赋值给 MutableList<out Number>
}

协变的作用: 允许将MutableList<Int>赋值给MutableList<out Number>,因为只能读取,不会破坏类型安全。

10.3 泛型in(逆变)

泛型in用于表示只能将值写入泛型类型,不能从泛型类型中读取值。这称为逆变(Contravariance)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// in T 表示T只能作为参数类型(只能写入)
fun writeToList(list: MutableList<in Number>) {
// 可以写入
list.add(10)
list.add(3.14)

// 读取时只能得到Any?类型
val item: Any? = list[0]
println(item)
}

fun main() {
val anyList = mutableListOf<Any>()
writeToList(anyList) // MutableList<Any> 可以赋值给 MutableList<in Number>
println(anyList) // 输出:[10, 3.14]
}

逆变的作用: 允许将MutableList<Any>赋值给MutableList<in Number>,因为只能写入Number及其子类,不会破坏类型安全。

10.4 泛型类型约束总结

关键字名称方向使用场景
out T协变只能读取(生产者)当你只需要从集合中读取数据时
in T逆变只能写入(消费者)当你只需要向集合中写入数据时
T不变可读可写当你需要同时读写数据时

记忆口诀:

  • out = 输出 = 读取 = 生产者
  • in = 输入 = 写入 = 消费者

10.5 where子句

where子句用于限制泛型类型的范围,可以指定多个约束条件。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
// 单个约束
fun <T : Number> sum(a: T, b: T): Double {
return a.toDouble() + b.toDouble()
}

// 多个约束使用where子句
fun <T> process(value: T) where T : Number, T : Comparable<T> {
println("值:$value")
println("比较:${value.compareTo(value)}")
}

// 示例:要求T必须是Number且实现了Comparable接口
class Calculator<T> where T : Number, T : Comparable<T> {
fun max(a: T, b: T): T {
return if (a > b) a else b
}
}

fun main() {
println(sum(10, 20)) // 输出:30.0
println(sum(3.14, 2.86)) // 输出:6.0

val calc = Calculator<Int>()
println("最大值:${calc.max(10, 20)}") // 输出:20
}

十一、inline函数

11.1 inline函数基础

inline(内联)函数是一个性能优化关键字,核心作用是:把函数的代码直接”复制粘贴”到调用处,而非传统的函数调用。

普通函数调用过程

当你调用一个普通函数时,JVM会做这些事:

  1. 跳转到函数的内存地址
  2. 执行函数代码
  3. 执行完跳回调用处

这个”跳转”过程有微小的性能开销(尤其是函数体很短、调用次数极多的时候)。

inline函数的优势

给函数加inline关键字后,编译器会在编译阶段:

  1. 把函数的全部代码直接复制到调用的地方
  2. 没有函数跳转,直接执行代码

特别适合lambda表达式: 因为lambda本质是匿名类对象,每次调用都会创建临时对象,带来额外开销;而inline会把lambda的代码也一起内联,消除lambda的对象创建开销。

11.2 inline函数示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// 1. 非inline函数(传统调用)
fun calculate(a: Int, b: Int, op: (Int, Int) -> Int): Int {
return op(a, b)
}

// 2. inline函数(内联)
inline fun inlineCalculate(a: Int, b: Int, op: (Int, Int) -> Int): Int {
return op(a, b)
}

fun main() {
// 调用非inline函数 → JVM会跳转执行calculate
val result1 = calculate(10, 20) { x, y -> x + y }
println(result1) // 输出:30

// 调用inline函数 → 编译器直接把inlineCalculate的代码复制到这里
val result2 = inlineCalculate(10, 20) { x, y -> x + y }
println(result2) // 输出:30
}

11.3 inline函数的实际应用

Kotlin标准库中大量使用了inline函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// mutableListOf的inline实现
inline fun <T> mutableListOf(vararg elements: T): MutableList<T> {
return ArrayList(elements.asList())
}

// forEach的inline实现
inline fun <T> Iterable<T>.forEach(action: (T) -> Unit): Unit {
for (element in this) action(element)
}

// 使用示例
fun main() {
val list = mutableListOf(1, 2, 3, 4, 5)

// forEach是inline函数,lambda会被内联
list.forEach {
println(it * 2)
}
}

11.4 inline函数使用建议

适合使用inline的场景:

  1. 函数体很短
  2. 函数接收lambda参数
  3. 函数被频繁调用

不适合使用inline的场景:

  1. 函数体很长(会导致代码膨胀)
  2. 函数是递归函数
  3. 函数没有lambda参数且函数体较复杂

11.5 noinline和crossinline

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// noinline:禁止内联特定的lambda参数
inline fun mixedInline(
a: Int,
inlineOp: (Int) -> Int, // 会被内联
noinline noInlineOp: (Int) -> Int // 不会被内联
): Int {
return noInlineOp(inlineOp(a))
}

// crossinline:允许内联但不能使用非局部返回
inline fun crossinlineExample(crossinline action: () -> Unit) {
Runnable { action() }.run()
}

fun main() {
val result = mixedInline(10, { it * 2 }, { it + 10 })
println(result) // 输出:30
}

十二、lambda表达式

12.1 lambda表达式基础

12.1.1 什么是lambda表达式

lambda表达式是一种匿名函数,它可以作为参数传递给其他函数或存储在变量中。

12.1.2 lambda表达式的语法

1
2
3
4
5
6
7
8
9
10
11
12
13
// 基本语法:{ 参数列表 -> 函数体 }
val add: (Int, Int) -> Int = { a, b -> a + b }

// 省略参数类型(如果可以从上下文推断)
val add2: (Int, Int) -> Int = { a, b -> a + b }

// 单参数可以省略参数列表
val double: (Int) -> Int = { it * 2 }

// 无参数的lambda表达式
val printHello: () -> Unit = { println("Hello!") }

val mylambda= { a : Int -> println(a) }

12.1.3 apply函数

apply函数是一个作用域函数,它可以在对象上执行一系列操作并返回该对象本身。
with函数不同,apply函数不返回lambda表达式的结果,而是返回对象本身。

1
2
3
4
5
6
// 使用apply函数初始化对象
val person = Person().apply {
name = "张三"
age = 30
address = "中国"
}

12.1.4 also函数

also 和 apply 都是用于简化对象操作的作用域函数,核心都是让你在不重复写对象名的前提下对对象进行操作

apply:对象初始化的 “专属工具”
apply 的上下文是 this(可以省略),非常适合给对象设置属性、调用初始化方法,写完后返回原对象,是 Kotlin 中初始化对象最常用的函数。
also 的上下文是 it(必须显式写,除非用不到),适合在不修改对象内部属性的前提下,对对象执行附加操作(比如打印日志、校验、存入集合、传参等)。

1
2
3
4
5
6
// 使用also函数初始化对象
val person = Person().also {
it.name = "张三"
it.age = 30
it.address = "中国"
}

12.1.5 let函数

let 同样是核心的作用域函数,和你刚了解的 also/apply 相比,它的核心特征是改变返回值 + 用 it 引用对象.
空安全处理(最常用场景)结合 ?. 操作符,let 可以优雅地处理 “对象非空时才执行操作” 的逻辑,避免繁琐的 if (obj != null) 判断.

1
2
3
4
5
6
7
// 使用let函数初始化对象
val person = Person().let {
it.name = "张三"
it.age = 30
it.address = "中国"
it
}

12.1.6 thread线程

thread 是 Kotlin 标准库中用于创建和启动线程的函数。它可以在后台执行耗时操作,避免阻塞主线程。

线程依赖包导入
dependencies {
implementation(“org.jetbrains.kotlinx:kotlinx-coroutines-core:1.6.4”)
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 使用thread函数创建并启动线程
thread {
// 后台执行的代码
println("后台线程执行中")
}

Thread.sleep(1000) // 线程 sleep 1 秒
Thread.currentThread().name // 获取当前线程名称
val parentJob = coroutineScope(default).launch {
delay(1000) // 协程 sleep 1 秒
println("子协程执行中")
launch {
println(Thread.currentThread().name)
println("子子协程执行中")
}
}
parentJob.join() // 等待子协程执行完毕

12.1.7 System

// 测量代码执行时间
val start = System.currentTimeMillis()

async 是 Kotlin 标准库中用于创建和启动异步任务的函数。它可以在后台执行耗时操作,避免阻塞主线程。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
// 使用async函数创建并启动异步任务
val deferredResult = async {
// 异步执行的代码
delay(1000) // 模拟耗时操作
return@async "异步任务完成"
}

// 等待异步任务完成并获取结果
val result = deferredResult.await()
println(result) // 输出:异步任务完成

// 使用globalScope.launch创建并启动全局协程
globalScope.launch {
delay(1000) // 协程 sleep 1 秒
println("子协程执行中")
}
// 使用withTimeoutOrNull函数设置超时时间
withTimeoutOrNull(1000) {
delay(2000) // 协程 sleep 2 秒
println("子协程执行中")
}

// 使用runBlocking函数创建并启动阻塞当前线程的协程
runBlocking {
delay(1000) // 协程 sleep 1 秒
println("子协程执行中")
}

CoroutineExceptionHandler 是 Kotlin 标准库中用于处理协程异常的类。它可以在协程中捕获异常并进行处理,避免异常导致程序崩溃。
```kotlin
// 使用CoroutineExceptionHandler处理协程异常
val exceptionHandler = CoroutineExceptionHandler { _, exception ->
println("捕获到协程异常:$exception")
}

// 在协程中使用exceptionHandler
val job = coroutineScope(default).launch(exceptionHandler) {
throw Exception("模拟异常")
}

// 等待协程执行完毕
job.join()


invoke 是 Kotlin 标准库中用于调用函数的函数。它可以在 lambda 表达式中调用其他函数,也可以在协程中调用其他函数。
```kotlin
// 使用invoke函数调用其他函数
fun sayHello(name: String) {
println("Hello, $name!")
}

// 在lambda表达式中调用sayHello函数
val lambda = { name: String -> sayHello(name) }
lambda.invoke("张三") // 输出:Hello, 张三!

// 在协程中调用sayHello函数
coroutineScope(default).launch {
sayHello("李四") // 输出:Hello, 李四!
}

invokeOnCompletion 是 Kotlin 标准库中用于在协程完成时调用的函数。它可以在协程中注册一个回调函数,当协程完成时(无论是否异常),都会调用该回调函数。
```kotlin
// 使用invokeOnCompletion函数在协程完成时调用回调函数
val job = coroutineScope(default).launch {
delay(1000) // 协程 sleep 1 秒
println("子协程执行中")
}

// 注册回调函数
job.invokeOnCompletion { exception ->
if (exception == null) {
println("协程执行完成")
} else {
println("协程执行异常:$exception")
}
}

// 等待协程执行完毕
job.join()

withContext 是 Kotlin 标准库中用于切换协程上下文的函数。它可以在协程中切换到不同的线程池或上下文,以执行耗时操作。
```kotlin
// 使用withContext函数切换协程上下文
val job = coroutineScope(default).launch {
delay(1000) // 协程 sleep 1 秒
println("子协程执行中")
}

// 切换到不同的线程池执行
withContext(Dispatchers.IO) {
delay(1000) // 协程 sleep 1 秒
println("子协程执行中")
}

// 等待协程执行完毕
job.join()

suspend 是 Kotlin 标准库中用于标记挂起函数的关键字。挂起函数可以在协程中暂停执行,等待异步操作完成后继续执行。
```kotlin
// 使用suspend函数标记挂起函数
suspend fun doSomethingAsync(): String {
delay(1000) // 模拟耗时操作
return "异步操作完成"
}

// 在协程中调用挂起函数
coroutineScope(default).launch {
val result = doSomethingAsync()
println(result) // 输出:异步操作完成
}

cancel 是 Kotlin 标准库中用于取消协程的函数。它可以在协程中调用,以取消正在执行的操作。
```kotlin
// 使用cancel函数取消协程
val job = coroutineScope(default).launch {
delay(1000) // 协程 sleep 1 秒
println("子协程执行中")
}

// 取消协程
job.cancel()

// 等待协程执行完毕
job.join()

十三、总结

本教程涵盖了Kotlin的核心基础知识,包括:

  1. 基础语法:变量声明、数据类型、空安全
  2. 函数:参数默认值、可变参数
  3. 控制流:循环、条件判断、when表达式
  4. 面向对象:类、构造函数、继承、接口、抽象类
  5. 高级特性:单例、委托、延迟初始化、数据类
  6. 集合操作:可变集合与不可变集合、过滤、映射、排序、统计等
  7. 类型转换与泛型:as关键字、泛型协变逆变、where子句
  8. 性能优化:inline函数、noinline、crossinline
  9. 集合操作:可变列表、不可变列表、数组、映射、集合操作符
  10. 异常处理:try-catch-finally、异常类型、自定义异常
  11. 文件操作:文件读取、写入、删除、遍历目录
  12. 并发编程:线程、线程池、协程、通道、流
  13. 数据库操作:JDBC、Room数据库、Kotlin协程数据库操作

Kotlin是一门现代化的编程语言,具有简洁、安全、互操作性强等特点。掌握这些基础知识后,你可以进一步学习Kotlin的高级特性,如协程、扩展函数、高阶函数等。


Kotlin学习资源

希望本教程对你有所帮助!如有问题,欢迎交流讨论。