重学Kotlin(一)变量与类型
一、变量声明
在 Kotlin 中,变量声明的方式和 Java 有一些不同,它更简洁、安全,也有明确的可变性区分。下面我帮你整理成一份 全面的 Kotlin 变量声明指南:
1. 可变与不可变变量
Kotlin 中有两种关键字:
| 关键字 | 描述 | 示例 |
|---|---|---|
val | 不可变(只读变量,相当于 Java 的 final) | val x: Int = 10 |
var | 可变(可修改) | var y: Int = 5 |
例子:
val a: Int = 10 // a 是不可变的,不能重新赋值
var b: Int = 5 // b 是可变的,可以重新赋值
b = 20 // 正常
// a = 15 // 编译错误!
小结:尽量用
val,只有在需要改变值时才用var,提高代码安全性。
2. 类型声明方式
Kotlin 支持两种方式声明类型:
(1)显式声明类型
val name: String = "Alice"
var age: Int = 20
(2)类型推断(推荐)
如果编译器能推断出类型,可以省略:
val name = "Alice" // 自动推断为 String
var age = 20 // 自动推断为 Int
3. 延迟初始化
有些场景下,变量在声明时不能立即初始化,可以使用:
(1)lateinit(只适用于 var 且非基本类型)
lateinit var userName: String
fun initUser() {
userName = "Bob"
}
fun printUser() {
println(userName)
}
注意:
lateinit不能用于val或基本类型 (Int,Double等)
(2)by lazy(延迟初始化且只读)
val lazyValue: String by lazy {
println("Computing...")
"Hello"
}
fun main() {
println(lazyValue) // 第一次访问时计算
println(lazyValue) // 直接使用缓存
}
4. 可空类型
Kotlin 中默认变量 不允许为 null,如果需要 null,需要使用 ?:
var name: String? = null // 可空类型
name = "Alice"
name = null // 合法
使用可空类型时,可以用安全调用 ?. 或非空断言 !!:
println(name?.length) // 安全调用,如果 name 为 null 返回 null
println(name!!.length) // 非空断言,如果 name 为 null 会抛异常
5. 常量(编译期常量)
- 使用
const val声明 编译期常量(只能在顶层或对象中):
const val MAX_COUNT = 100
6. 总结
| 场景 | 关键字/方式 |
|---|---|
| 不可变变量 | val |
| 可变变量 | var |
| 延迟初始化可变 | lateinit var |
| 延迟初始化只读 | val ... by lazy { } |
| 可空变量 | ? |
| 编译期常量 | const val |
| 类型推断 | 可以省略类型,Kotlin 自动推断 |
二、基本数据类型
| 类型说明 | Kotlin 类型 | 对应的 Java 基本类型 | Java 包装类型 |
|---|---|---|---|
| 8 位有符号整数 | Byte | byte | Byte |
| 16 位有符号整数 | Short | short | Short |
| 32 位有符号整数 | Int | int | Integer |
| 64 位有符号整数 | Long | long | Long |
| 单精度浮点数 | Float | float | Float |
| 双精度浮点数 | Double | double | Double |
| 单个字符 | Char | char | Character |
| 布尔值 | Boolean | boolean | Boolean |
1. Kotlin 与 Java 的区别
在 Java 中:
- 有 基本类型(primitive types):
int,long,float,double,boolean,char等。这些类型在 JVM 层面上 不是对象,存储在栈上,操作高效。 - 还有 引用类型(reference types / class):
Integer,Long,Boolean等,是对象,存储在堆上。 - 问题:
- 基本类型不能直接调用方法(需要装箱成对象)。
- Java 类型体系不统一(有基本类型和对象类型的差异)。
- 泛型只能用对象类型(例如
List<Integer>,不能用List<int>)。
在 Kotlin 中:
-
Kotlin 没有 Java 风格的基本类型,只有统一的 类类型:
val a: Int = 5 val b: Boolean = true在语法上,这看起来都是对象类型,可以直接调用方法:
val a: Int = 5 println(a.toString()) // 正常调用 -
Kotlin 的类型系统 统一了基本类型和对象类型,开发者不必区分
int和Integer,Kotlin 的Int就是一个类。println(Int::class) // 输出 class kotlin.Int
2. Kotlin 运行时优化(JVM 编译器做的事)
虽然 Kotlin 在语法上只有对象类型,但编译器在编译到 JVM 字节码时,会做 自动优化(under the hood):
-
对于局部变量、数组等,Kotlin 会尽可能使用 JVM 的基本类型(primitive types),避免性能损失:
val x: Int = 123- JVM 上可能会直接用
int存储。
- JVM 上可能会直接用
-
对于泛型或需要对象的场景,Kotlin 会自动装箱:
val list: List<Int> = listOf(1, 2, 3) // 会用 Integer -
总结:
- 语法统一(统一对象类型) → 简化开发、避免基本类型和对象类型切换。
- 编译优化 → 在 JVM 层面仍然保持性能,不损失效率。
3. Kotlin 没有基本类型的好处
| 优点 | 说明 |
|---|---|
| 统一类型系统 | 所有类型都是对象,方法调用统一,泛型支持直观。 |
| 减少装箱/拆箱的困扰 | 编译器自动优化,开发者无需手动转换。 |
| 安全性更高 | 不存在基本类型和对象类型之间混用产生的空指针问题(Kotlin 的 Int 永远非空,Int? 才可能为空)。 |
| 简化泛型使用 | 泛型参数可以直接使用 Int、Boolean 等类型,不用考虑 Java 的限制。 |
- 举个例子
fun sum(a: Int, b: Int): Int = a + b
fun main() {
val x: Int = 10
val y: Int = 20
println(sum(x, y)) // 30
}
- Kotlin 里
Int是对象,直接调用toString()等方法都可以。 - 编译到 JVM 字节码时,局部变量
x、y仍可能是int,性能无损。
4.类型转化
在 Java 中,基本类型之间可以自动转换(隐式转换):
int i = 10;
long l = i; // 自动提升
但在 Kotlin 中,这样写会报错:
val i: Int = 10
val l: Long = i // 编译错误:类型不匹配
原因: Kotlin 中所有类型(包括 Int、Long、Double)都是 对象类型, 没有 Java 的“自动提升”。
这样做是为了:
- 避免自动转换带来的精度丢失或隐式错误;
- 保证类型安全;
- 让所有类型行为一致(对象思维)。
总结
- Kotlin 语法上没有基本类型,统一使用对象类型(Int、Boolean 等都是类)。
- 编译器会自动在 JVM 上优化,局部变量和数组仍可能用基本类型存储。
- 这样设计主要是为了类型系统统一、泛型安全、避免装箱/拆箱烦恼。
- 相较java的基本数据类型转化,kotlin没有隐式转换,必须调用相关函数显式的转化
三、String类型
Kotlin 的
String本质上还是Java 的java.lang.String, 但 Kotlin 语法层面对它进行了 扩展(增强方法 + 空安全 + 模板语法)。
1.字符串模板(Kotlin 独有)
这是 Java 没有的语法糖。
Kotlin 示例:
val name = "Tom"
val age = 20
println("My name is $name, I am $age years old.")
输出:
My name is Tom, I am 20 years old.
也可以嵌入表达式:
println("Next year I’ll be ${age + 1}")
Java 中只能这样写:
System.out.println("My name is " + name + ", I am " + age + " years old.");
2.字符串比较
| 操作 | Java | Kotlin |
|---|---|---|
| 按内容比较 | s1.equals(s2) | s1 == s2(自动调用 equals |
| 按引用比较 | s1 == s2 | s1 === s2 |
| 忽略大小写 | equalsIgnoreCase() | equals(other, ignoreCase = true) |
示例:
val s1 = "abc"
val s2 = "ABC"
println(s1 == s2) // false
println(s1.equals(s2, ignoreCase = true)) // true
3.字符串拼接与效率
| 对比项 | Java | Kotlin |
|---|---|---|
| 简单拼接 | + 运算符 | 同样支持 + |
| 编译优化 | StringBuilder 拼接优化 | 通过 StringBuilder 优化(底层同 Java) |
| 多行文本 | 需要 \n 拼接 | ✅ 支持原生多行字符串(""" """) |
Kotlin 多行字符串示例:
val text = """
Line1
Line2
Line3
""".trimIndent()
Java 15+ 才引入类似的文本块(Text Block):
String text = """
Line1
Line2
Line3
""";
四、数组
1.数组创建
val arr = arrayOf(1, 2, 3, 4, 5)
arrayOf()是一个泛型函数,返回Array<T>类型。- 例如
Array<Int>、Array<String>等。
指定类型:
val arr: Array<String> = arrayOf("A", "B", "C")
指定大小 + 初始值生成数组
val squares = Array(5) { i -> i * i }
println(squares.joinToString()) // 0, 1, 4, 9, 16
Array(size) { index -> 初始化表达式 },这里是个内联函数后续再分析- 注意这里返回的依然是
Array<Int>,而不是IntArray。
2.基本类型数组(原生类型数组)
为了避免装箱/拆箱的性能开销,Kotlin 为基本类型提供了专门的数组类型:
| Kotlin 数组类型 | 对应 Java 类型 | 创建方式 |
|---|---|---|
IntArray | int[] | intArrayOf(1, 2, 3) |
LongArray | long[] | longArrayOf(1L, 2L) |
DoubleArray | double[] | doubleArrayOf(1.0, 2.0) |
FloatArray | float[] | floatArrayOf(1f, 2f) |
CharArray | char[] | charArrayOf('a', 'b') |
ByteArray | byte[] | byteArrayOf(1, 2) |
BooleanArray | boolean[] | booleanArrayOf(true, false) |
3.数组的常用操作
val arr = arrayOf(10, 20, 30)
println(arr[0]) // 访问元素:10
arr[1] = 50 // 修改元素
println(arr.size) // 长度:3
遍历:
for (item in arr) {
println(item)
}
// 或者使用索引
for (i in arr.indices) {
println("arr[$i] = ${arr[i]}")
}
4.常用函数
Kotlin 提供了丰富的扩展函数:
| 函数 | 作用 | 示例 |
|---|---|---|
first() / last() | 取首尾元素 | arr.first() |
sum() | 求和(数值类型) | nums.sum() |
average() | 平均值 | nums.average() |
max() / min() | 最大/最小 | nums.maxOrNull() |
contains() | 是否包含 | arr.contains(3) |
sorted() | 排序后返回新数组 | arr.sorted() |
reversed() | 反转 | arr.reversed() |
map {} | 映射 | arr.map { it * 2 } |
forEach {} | 遍历 | arr.forEach { println(it) } |
5.与 Java 的互操作
在 Kotlin 调用 Java 代码时:
IntArray↔int[]Array<Int>↔Integer[]
⚠️ 区别:
val a1: Array<Int> = arrayOf(1, 2, 3) // 装箱类型 Integer[]
val a2: IntArray = intArrayOf(1, 2, 3) // 原生类型 int[]
当你调用 Java 方法时,若它期望 int[],必须传 IntArray。
6.二维数组示例
val matrix = Array(3) { IntArray(3) } // 3x3 矩阵
matrix[0][1] = 5
println(matrix[0].joinToString()) // 输出 0, 5, 0
五、区间
1.区间的基本语法
(1).. 表示「闭区间」
即:包含起点和终点
val range = 1..5
println(range.toList()) // [1, 2, 3, 4, 5]
相当于数学上的 [1, 5]
(2)until 表示「半开区间」
即:包含起点,不包含终点
val range = 1 until 5
println(range.toList()) // [1, 2, 3, 4]
相当于数学上的 [1, 5)
常用于循环: 因为索引常常是从 0 开始、到 size-1 结束:
for (i in 0 until list.size) { ... }
(3)downTo 表示「递减区间」
val range = 5 downTo 1
println(range.toList()) // [5, 4, 3, 2, 1]
(4)step 表示「步长」
val range = 1..10 step 2
println(range.toList()) // [1, 3, 5, 7, 9]
step 可用于任意区间(包括递减区间):
for (i in 10 downTo 0 step 3) {
print("$i ") // 10 7 4 1
}
2.常见类型的区间
| 类型 | 示例 | 说明 |
|---|---|---|
IntRange | 1..5 | 整型区间 |
LongRange | 1L..5L | 长整型区间 |
CharRange | 'a'..'z' | 字符区间 |
UIntRange | 1u..10u | 无符号整型区间 |
ULongRange | 1uL..10uL | 无符号长整型区间 |
字符区间很常用:
for (ch in 'a'..'f') print(ch) // abcdef
3.区间的常用方法
Kotlin 的区间类都实现了 ClosedRange<T> 接口,常用方法有:
| 方法 | 说明 | 示例 |
|---|---|---|
first | 起点 | (1..5).first → 1 |
last | 终点 | (1..5).last → 5 |
contains(x) 或 in | 是否包含 | 3 in 1..5 → true |
isEmpty() | 是否为空 | (5..1).isEmpty() → true |
reversed() | 反转区间 | (1..5).reversed() → 5 downTo 1 |
4.for 循环中的区间
for (i in 1..5) print("$i ") // 1 2 3 4 5
for (i in 1 until 5) print("$i ") // 1 2 3 4
for (i in 5 downTo 1) print("$i ") // 5 4 3 2 1
5.in 与 !in 判断运算符
区间经常和 in 一起用:
val x = 3
if (x in 1..5) {
println("x 在区间内")
}
if (x !in 10..20) {
println("x 不在区间内")
}
也支持字符比较:
if ('k' in 'a'..'z') println("是小写字母")
6.区间的本质
在 Kotlin 中:
val range = 1..5
实际上是:
val range = IntRange(1, 5)
.. 操作符其实是调用了一个函数:
operator fun Int.rangeTo(that: Int): IntRange
所以:
1..5→ 调用1.rangeTo(5)'a'..'z'→ 调用'a'.rangeTo('z')
7.区间的高级用法示例
(1)检查索引合法性
if (index in 0 until list.size) {
println("合法索引")
}
(2)日期或自定义类也能比较(前提是实现 Comparable)
val start = LocalDate.of(2025, 1, 1)
val end = LocalDate.of(2025, 12, 31)
val today = LocalDate.now()
if (today in start..end) {
println("在今年内")
}
六、集合框架
1.Kotlin 集合框架总览
Kotlin 的集合主要分为两大类:
| 类型 | 可变性 | 常见实现 | 对应 Java 类 |
|---|---|---|---|
只读集合 (List, Set, Map) | 不可修改(只读视图) | listOf(), setOf(), mapOf() | Collections.unmodifiableList() 等 |
可变集合 (MutableList, MutableSet, MutableMap) | 可添加、删除、修改 | mutableListOf(), mutableSetOf(), mutableMapOf() | ArrayList, HashSet, HashMap |
Kotlin 并没有自己实现底层数据结构,而是封装了 Java 集合类,并提供了更安全的接口。
2.常用创建方式
// 只读集合
val list = listOf("A", "B", "C")
val set = setOf(1, 2, 3)
val map = mapOf("key1" to 1, "key2" to 2)
// 可变集合
val mutableList = mutableListOf("A", "B", "C")
mutableList.add("D")
val mutableSet = mutableSetOf(1, 2, 3)
mutableSet.remove(2)
val mutableMap = mutableMapOf("a" to 1, "b" to 2)
mutableMap["c"] = 3
3.核心操作(函数式风格)
Kotlin 集合支持 链式操作:
val nums = listOf(1, 2, 3, 4, 5)
// 过滤 + 映射
val result = nums.filter { it % 2 == 0 } // [2, 4]
.map { it * it } // [4, 16]
// 求和、平均
nums.sum() // 15
nums.average() // 3.0
// 分组
nums.groupBy { it % 2 } // {1=[1, 3, 5], 0=[2, 4]}
// 去重、排序
nums.distinct() // 去重
nums.sortedDescending() // 排序
4.只读 vs 可变:编译期安全
Kotlin 的设计理念是:默认不可变,除非必要时可变。
val list = listOf(1, 2, 3)
list.add(4) // ❌ 编译错误:List 没有 add()
val mutableList = mutableListOf(1, 2, 3)
mutableList.add(4) // ✅
⚠️ 需要注意: “只读集合”并不意味着数据真的不可变,只是接口层面只读。如果底层仍然指向可变集合,数据仍可能被修改。
5.Java 兼容性
Kotlin 集合完全兼容 Java:
val javaList = java.util.ArrayList<String>()
val kotlinList: MutableList<String> = javaList // ✅ 自动映射
Kotlin 编译器会在编译期自动做类型桥接。
6.集合扩展函数(Kotlin 独有)
Kotlin 给所有集合类型都定义了很多扩展函数,如:
| 分类 | 常见函数 |
|---|---|
| 查找 | find, first, last, indexOf |
| 统计 | count, sum, average, maxBy |
| 转换 | map, flatMap, zip, associate |
| 过滤 | filter, filterNot, dropWhile |
| 聚合 | reduce, fold |
| 拆分 | partition, chunked, windowed |
例如:
val names = listOf("Tom", "Jerry", "Spike")
val shortNames = names.filter { it.length <= 3 }
val upper = names.map { it.uppercase() }