基础语法与类型
变量与常量(Variables and Constants)
在 Kotlin 中,声明变量的方式与 Java 有显著不同。Kotlin 强调 不可变性优先(immutability first) 的设计哲学。
val —— 不可变引用(Immutable Reference)
val 来自 "value" 的缩写,声明后不可重新赋值(cannot be reassigned)。
val name: String = "Kotlin"
name = "Java" // ❌ 编译错误:Val cannot be reassigned
val age = 18 // 类型推断(Type Inference):编译器自动推断为 Int⚠️ 重要区分:
val是引用不可变,而非对象本身不可变(The reference is immutable, not the object itself)
val list = mutableListOf(1, 2, 3)
list.add(4) // ✅ 可以修改对象内容
list = mutableListOf() // ❌ 不能重新赋值┌─────────────────────────────────────────────┐
│ val list ──────────► MutableList [1,2,3,4] │
│ ↑ ↑ │
│ 引用锁定 内容可变 │
│ (reference locked) (content mutable) │
└─────────────────────────────────────────────┘var —— 可变引用(Mutable Reference)
var 来自 "variable",可以重新赋值(reassignable)。
var count = 0
count = 10 // ✅ 可以重新赋值
count = "Hi" // ❌ 类型不能改变(Type mismatch)最佳实践(Best Practice):
"Prefer
valovervar. Usevaronly when necessary." 优先使用val,仅在必要时使用var。
lateinit —— 延迟初始化(Late Initialization)
用于非空变量的延迟初始化,常见于依赖注入(Dependency Injection)或单元测试场景。
class UserService {
lateinit var repository: UserRepository // 稍后初始化
fun setup() {
repository = UserRepository() // 实际初始化
}
fun loadUser() {
if (::repository.isInitialized) { // 检查是否已初始化
repository.findAll()
}
}
}使用限制(Constraints):
// 必须是类属性,不能是局部变量
lateinit val x: String // ❌ 必须是 var,lateinit val 不合法
lateinit var count: Int // ❌ 不能是基本类型,不能用于 Int、Boolean 等
lateinit var name: String? // ❌ 不能是可空类型,不能是 String?const —— 编译时常量(Compile-time Constant)
const val 的值在编译期确定(determined at compile time),会被直接内联到使用处。
// 必须在 top-level 或 object 中声明
const val MAX_SIZE = 100
const val API_URL = "https://api.example.com"
object Config {
const val TIMEOUT = 30_000 // ✅ object 内可以
}
class MyClass {
const val X = 10 // ❌ 普通类内部不行
companion object {
const val Y = 20 // ✅ companion object 可以
}
}const vs val 对比:
┌────────────────────────────────────────────────────────────┐
│ const val │
│ ┌──────────┐ 编译时 ┌──────────────────────┐ │
│ │ const=100│ ──────────► │ 使用处直接替换为 100 │ │
│ └──────────┘ inline └──────────────────────┘ │
├────────────────────────────────────────────────────────────┤
│ val │
│ ┌──────────┐ 运行时 ┌──────────────────────┐ │
│ │ val=计算值│ ──────────► │ 读取变量引用 │ │
│ └──────────┘ reference └──────────────────────┘ │
└────────────────────────────────────────────────────────────┘const 限制条件:
-
必须是
String或基本类型(Primitive types) -
必须在顶层(top-level)或
object/companion object中 -
不能有自定义 getter
📝 小练习
题目 1:以下哪个声明是合法的?
A. lateinit val name: String
B. lateinit var count: Int
C. lateinit var repo: Repository
D. const val time = System.currentTimeMillis()
【答案】C
【解析】
-
A 错误:
lateinit只能修饰var,不能修饰val -
B 错误:
lateinit不能用于基本类型Int -
C 正确:
lateinit var修饰非空、非基本类型的类属性 ✅ -
D 错误:
const必须是编译时常量,函数调用结果是运行时确定的
题目 2:关于 val 的说法,正确的是?
A. val 声明的对象内容不可修改
B. val 等价于 Java 的 final 变量
C. val 变量必须在声明时立即赋值
D. val 不支持类型推断
【答案】B
【解析】
-
A 错误:
val只是引用不可变,对象内容(如MutableList)可以修改 -
B 正确:
val编译后对应 Java 的final变量 ✅ -
C 错误:
val可以先声明后赋值(只要在使用前赋值即可),如类属性 -
D 错误:
val name = "Kotlin"自动推断为String类型
类型系统概览(Type System Overview)
Kotlin 拥有强静态类型系统(strong statically-typed system),同时引入了空安全(null safety)作为核心特性。
"In Kotlin, everything is an object." 在 Kotlin 中,一切皆对象。
基本类型(Primitive Types)
虽然从语言层面看 Kotlin 没有原始类型,但编译器会在可能时优化为 JVM 原始类型:
val a: Int = 42 // 编译为 JVM 的 int
val b: Int? = 42 // 编译为 JVM 的 Integer(装箱)
val c: Double = 3.14 // 编译为 JVM 的 doubleKotlin 基本类型列表:
| 类型 | 位宽 | 对应 JVM 类型 |
|---|---|---|
| Byte | 8-bit | byte / Byte |
| Short | 16-bit | short / Short |
| Int | 32-bit | int / Integer |
| Long | 64-bit | long / Long |
| Float | 32-bit | float / Float |
| Double | 64-bit | double / Double |
| Char | 16-bit | char / Character |
| Boolean | - | boolean / Boolean |
引用类型(Reference Types)
除基本类型外,其他都是引用类型,包括:
val str: String = "Hello" // 字符串
val list: List<Int> = listOf(1,2) // 集合
val user: User = User("Tom") // 自定义类
val func: (Int) -> Int = { it * 2 } // 函数类型(Function Type)类型层次结构(Type Hierarchy)
Kotlin 的类型层次设计精妙,理解它对掌握空安全至关重要:
┌─────────┐
│ Any │ ← 所有非空类型的根
└────┬────┘
│
┌───────────────────┼───────────────────┐
│ │ │
┌────┴────┐ ┌─────┴─────┐ ┌─────┴─────┐
│ String │ │ Int │ │ User │
└─────────┘ └───────────┘ └───────────┘
┌─────────┐
│ Any? │ ← 所有类型的根(含可空)
└────┬────┘
│
┌───────────────────┼───────────────────┐
│ │ │
┌────┴────┐ ┌─────┴─────┐ ┌─────┴─────┐
│ String? │ │ Int? │ │ Any/其他 │
└─────────┘ └───────────┘ └───────────┘
┌─────────┐
│ Nothing │ ← 所有类型的子类型(底部类型)
└─────────┘Any —— 非空类型的根(Root of Non-null Types)
val anything: Any = "Hello" // String 是 Any 的子类
val number: Any = 42 // Int 也是 Any 的子类
fun printAny(obj: Any) {
println(obj.toString()) // Any 有 toString()、hashCode()、equals()
}类似 Java 的 Object,但 Any 不接受 null。
Any? —— 可空类型的根(Root of Nullable Types)
val maybeNull: Any? = null // ✅ 可以是 null
val alsoOk: Any? = "Hello" // ✅ 也可以是非空值
fun process(input: Any?) {
// 必须做空检查才能调用方法
input?.toString()
}类型关系(Type Relationship):
Every type
Tis a subtype ofT?每个类型T都是T?的子类型
val s: String = "Hi"
val sn: String? = s // ✅ String 可以赋给 String?
val s2: String = sn // ❌ String? 不能直接赋给 StringUnit —— 无返回值类型(The Unit Type)
相当于 Java 的 void,但 Unit 是真正的类型,有唯一实例 Unit。
fun printHello(): Unit { // 可省略 ": Unit"
println("Hello")
}
val result: Unit = printHello() // 可以接收
println(result) // 输出: kotlin.UnitNothing —— 底部类型(Bottom Type)
表示永远不会返回的函数或永不存在的值。
fun fail(msg: String): Nothing {
throw IllegalArgumentException(msg) // 永远抛异常
}
fun infiniteLoop(): Nothing {
while (true) { } // 永远不结束
}Nothing 的类型论意义:
"Nothing is a subtype of every type." Nothing 是所有类型的子类型。
val x: String = fail("Error") // ✅ Nothing 兼容 String
val y: Int = fail("Error") // ✅ Nothing 兼容 Int这使得 throw 表达式可以用在任何地方:
val name = input ?: throw IllegalArgumentException()
// throw 返回 Nothing,而 Nothing 是 String 的子类型,所以类型检查通过类型层次完整图示
┌──────────────┐
│ Any? │ Nullable 根
└──────┬───────┘
│
┌───────────────────┴───────────────────┐
│ │
┌──────┴──────┐ ┌──────┴──────┐
│ Any │ │ 其他 T? │
│ (非空类型根) │ │ (可空类型) │
└──────┬──────┘ └─────────────┘
│
┌────────┼────────┬────────────┐
│ │ │ │
┌──┴──┐ ┌───┴──┐ ┌───┴──┐ ┌──────┴──────┐
│String││ Int │ │ Unit │ │ 其他非空类型 │
└─────┘ └──────┘ └──────┘ └─────────────┘
│ │ │ │
└────────┴────────┴────────────┘
│
┌──────┴──────┐
│ Nothing │ 底部类型(所有类型的子类型)
└─────────────┘📝 小练习
题目 1:关于 Kotlin 类型层次,下列说法正确的是?
A. Any 是所有类型(包括可空类型)的父类
B. Nothing 是所有类型的父类
C. Int 是 Int? 的子类型
D. Unit 等价于 Java 的 null
【答案】C
【解析】
-
A 错误:
Any只是非空类型的根,Any?才是所有类型(含可空)的根 -
B 错误:
Nothing是所有类型的子类型(subtype),不是父类 -
C 正确:任何类型
T都是其可空版本T?的子类型 ✅ -
D 错误:
Unit类似 Java 的void,表示"无有意义返回值",与null无关
题目 2:以下哪个函数返回类型应该是 Nothing?
A. fun getZero() = 0
B. fun printHi() { println("Hi") }
C. fun error(): ??? { throw Exception() }
D. fun nullable(): String? = null
【答案】C
【解析】
-
A:返回
Int(值为 0) -
B:返回
Unit(无有意义返回值,但正常结束) -
C 正确:函数永远抛出异常,不会正常返回,应声明为
Nothing✅ -
D:返回
String?(值为 null,但正常返回了)
数值类型(Numeric Types)
Kotlin 提供了一套完整的数值类型,覆盖整数(Integer)和浮点数(Floating-point)两大类。
"On the JVM, numbers are stored as primitive types unless a nullable reference or generics are involved." 在 JVM 上,数值会被存储为原始类型,除非涉及可空引用或泛型。
整数类型(Integer Types)
| 类型 | 位宽(Size) | 取值范围 |
|---|---|---|
| Byte | 8 bits | -128 ~ 127 |
| Short | 16 bits | -32,768 ~ 32,767 |
| Int | 32 bits | -2³¹ ~ 2³¹-1(约±21亿) |
| Long | 64 bits | -2⁶³ ~ 2⁶³-1 |
val byte: Byte = 127
val short: Short = 32767
val int: Int = 2_147_483_647
val long: Long = 9_223_372_036_854_775_807L // 注意 L 后缀类型推断规则(Type Inference Rules):
val a = 100 // 默认推断为 Int
val b = 3_000_000_000 // 超出 Int 范围,自动推断为 Long
val c = 100L // 显式声明为 Long(使用 L 后缀)┌──────────────────────────────────────────────────────────┐
│ 整数字面量的类型推断流程 │
├──────────────────────────────────────────────────────────┤
│ │
│ 字面量 ──► 有 L 后缀? ──► 是 ──► Long │
│ │ │
│ 否 │
│ ▼ │
│ 值在 Int 范围内? ──► 是 ──► Int │
│ │ │
│ 否 │
│ ▼ │
│ Long │
│ │
└──────────────────────────────────────────────────────────┘浮点类型(Floating-point Types)
val pi: Double = 3.141592653589793 // Double 64bits 15-16 位有效数字 | 科学计算/财务(需配合 BigDecimal)
val e: Float = 2.718281f // 注意 f/F 后缀 Float 32bits 6-7 位有效数字 | 图形/游戏/内存敏感
val defaultDouble = 3.14 // 默认推断为 Double
val explicitFloat = 3.14f // 必须加 f 才是 Float⚠️ 注意(Caution):浮点数默认是
Double,要得到Float必须使用f或F后缀。
字面量表示法(Literal Notations)
下划线分隔(Underscore Separators)
Kotlin 允许在数字中使用下划线提高可读性(for better readability):
val oneMillion = 1_000_000 // 一百万
val creditCard = 1234_5678_9012_3456L
val hexBytes = 0xFF_EC_DE_5E
val binaryMask = 0b1111_0000_1010
val socialSecurity = 999_99_9999"Underscores in numeric literals make them easier to read." 数字字面量中的下划线使其更易阅读。
进制表示(Radix Representation)
val decimal = 255
val hex = 0xFF // 十六进制 255 十六进制(Hexadecimal) 0x/0X 0x7F(127)
val binary = 0b1111_1111 // 二进制 255 二进制(Binary) 0b/0B 0b0111_1111(127)
println(decimal == hex) // true
println(hex == binary) // true⚠️ 注意:Kotlin 不支持八进制字面量(No octal literals)。Java 的
0777写法在 Kotlin 中不合法。
数值类型存储机制(Storage Mechanism)
┌─────────────────────────────────────────────────────────────┐
│ Kotlin 数值在 JVM 上的存储 │
├─────────────────────────────────────────────────────────────┤
│ │
│ val a: Int = 100 │
│ ┌─────────┐ │
│ │ int │ ◄── JVM primitive(栈上直接存储) │
│ └─────────┘ │
│ │
│ val b: Int? = 100 │
│ ┌─────────┐ ┌─────────────┐ │
│ │ ref │────►│ Integer │ ◄── 装箱对象(堆上) │
│ └─────────┘ └─────────────┘ │
│ │
│ val list: List<Int> = listOf(1, 2, 3) │
│ ┌─────────┐ ┌───────────────────────┐ │
│ │ ref │────►│ [Integer, Integer...] │ ◄── 泛型需装箱 │
│ └─────────┘ └───────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────┘装箱与相等性(Boxing and Equality):
val a: Int = 1000
val b: Int = 1000
println(a == b) // true(值相等)
val boxedA: Int? = a
val boxedB: Int? = a
println(boxedA == boxedB) // true(值相等,使用 equals)
println(boxedA === boxedB) // false ⚠️ 可能不同对象(引用相等)重要:
==比较值(equals),===比较引用(referential equality)。装箱后可能产生不同对象。
📝 小练习
题目 1:以下哪个数值字面量是合法的 Kotlin 代码?
A. val oct = 0777
B. val hex = 0xGG
C. val bin = 0b1012
D. val num = 1_000_000L
【答案】D
【解析】
-
A 错误:Kotlin 不支持八进制字面量(没有
0开头的八进制写法) -
B 错误:十六进制只能包含 0-9 和 A-F,
G不合法 -
C 错误:二进制只能包含 0 和 1,
2不合法 -
D 正确:使用下划线分隔的 Long 字面量,完全合法 ✅
题目 2:以下代码输出什么?
val a: Int = 128
val b: Int? = 128
val c: Int? = 128
println(b === c)A. true
B. false
C. 编译错误
D. 运行时异常
【答案】B
【解析】
-
===是引用相等判断(referential equality) -
Int?是可空类型,在 JVM 上会被装箱为Integer对象 -
128 超出了 Integer 缓存范围(-128 ~ 127),所以
b和c是两个不同的对象 -
因此
b === c返回false -
如果值是 127,由于 Integer 缓存,结果可能是
true
类型转换(Type Conversion)
Kotlin 是强类型语言(strongly-typed language),数值类型之间不会自动隐式转换。
"Smaller types are NOT implicitly converted to bigger types." 较小的类型不会隐式转换为较大的类型。
val byte: Byte = 1
val int: Int = byte // ❌ 编译错误!Type mismatch
val int2: Int = byte.toInt() // ✅ 必须显式转换显式转换函数(Explicit Conversion Functions)
每种数值类型都提供以下转换方法:
val number = 42
number.toByte() // → Byte
number.toShort() // → Short
number.toInt() // → Int
number.toLong() // → Long
number.toFloat() // → Float
number.toDouble() // → Double
number.toChar() // → Char(对应 Unicode 码点)转换示例(Conversion Examples):
val long: Long = 100L
val int: Int = long.toInt() // Long → Int
val double: Double = 3.99
val int2: Int = double.toInt() // 3(截断,不是四舍五入!)
val int3: Int = 65
val char: Char = int3.toChar() // 'A'(ASCII 65)精度损失警告(Precision Loss):
┌─────────────────────────────────────────────────────────────┐
│ 类型转换方向与风险 │
├─────────────────────────────────────────────────────────────┤
│ │
│ Byte → Short → Int → Long → Float → Double │
│ ────────────────────────────────► │
│ 扩展转换(Widening)安全 │
│ │
│ Double → Float → Long → Int → Short → Byte │
│ ────────────────────────────────► │
│ 收缩转换(Narrowing)可能丢失精度 │
│ │
└─────────────────────────────────────────────────────────────┘val big: Long = 10_000_000_000L
val small: Int = big.toInt()
println(small) // 1410065408 ⚠️ 溢出!不是原值
val precise: Double = 3.999999999999
val less: Float = precise.toFloat()
println(less) // 4.0 ⚠️ 精度丢失智能转换(Smart Casts)
Kotlin 编译器能够追踪类型检查(track type checks),自动进行类型转换,无需手动强转。
"Smart casts work for
vallocal variables andvalproperties that are not open." 智能转换适用于局部val变量和非 open 的val属性。
fun processAny(obj: Any) {
if (obj is String) {
// 在这个分支中,obj 自动转换为 String
println(obj.length) // ✅ 直接调用 String 的方法
println(obj.uppercase()) // ✅ 无需手动转换
}
if (obj !is String) return
// 到这里,编译器知道 obj 一定是 String
println(obj.length) // ✅ 智能转换生效
}智能转换流程(Smart Cast Flow):
┌─────────────────────────────────────────────────────────────┐
│ 智能转换工作原理 │
├─────────────────────────────────────────────────────────────┤
│ │
│ fun demo(x: Any) { │
│ │ │
│ ▼ │
│ ┌──────────────┐ │
│ │ x is String? │ │
│ └──────┬───────┘ │
│ │ │
│ ┌────┴────┐ │
│ ▼ ▼ │
│ [YES] [NO] │
│ │ │ │
│ ▼ ▼ │
│ x: String x: Any ← 编译器自动推断分支内的类型 │
│ │ │ │
│ x.length ❌ x.length │
│ ✅ │
│ │
└─────────────────────────────────────────────────────────────┘when 表达式中的智能转换:
fun describe(obj: Any): String = when (obj) {
is Int -> "Integer: ${obj * 2}" // obj 是 Int
is String -> "String of length ${obj.length}" // obj 是 String
is List<*> -> "List with ${obj.size} elements" // obj 是 List
else -> "Unknown"
}智能转换的限制(Limitations):
class Example {
var mutableProp: Any = "Hello"
fun test() {
if (mutableProp is String) {
// ❌ 智能转换不可用!
// mutableProp.length
// 因为 var 可能在检查后被其他线程修改
}
}
}| 场景 | 智能转换是否生效 |
|---|---|
| val 局部变量 | ✅ 生效 |
| val 不可覆盖属性 | ✅ 生效 |
| var 局部变量(检查后未修改) | ✅ 生效 |
| var 属性 | ❌ 不生效 |
| open val 属性 | ❌ 不生效 |
安全转换(Safe Casts)
使用 as? 进行安全类型转换,失败时返回 null 而不是抛异常。
val obj: Any = "Hello"
// 不安全转换(Unsafe Cast)
val str1: String = obj as String // ✅ 成功
val int1: Int = obj as Int // ❌ ClassCastException!
// 安全转换(Safe Cast)
val str2: String? = obj as? String // ✅ 返回 "Hello"
val int2: Int? = obj as? Int // ✅ 返回 null(不会崩溃)对比三种转换方式(Comparison):
fun process(input: Any) {
// 方式1:is + 智能转换(推荐)
if (input is String) {
println(input.length)
}
// 方式2:as 强制转换(可能崩溃)
val str = input as String // 危险!
// 方式3:as? 安全转换 + 空处理
val strSafe = input as? String
println(strSafe?.length ?: "Not a String")
}┌─────────────────────────────────────────────────────────────┐
│ 类型转换方式对比 │
├──────────────┬──────────────┬───────────────────────────────┤
│ 方式 │ 语法 │ 结果 │
├──────────────┼──────────────┼───────────────────────────────┤
│ 智能转换 │ is + 分支 │ 自动转换,最安全 │
│ 强制转换 │ as │ 失败抛 ClassCastException │
│ 安全转换 │ as? │ 失败返回 null │
└──────────────┴──────────────┴───────────────────────────────┘数值运算中的类型提升(Type Promotion in Operations)
虽然赋值不会隐式转换,但运算时会自动类型提升:
val byte: Byte = 1
val int: Int = 2
val result = byte + int // result 类型是 Int(自动提升)
val long: Long = 10L
val sum = int + long // sum 类型是 Long
val float: Float = 1.5f
val double: Double = 2.5
val total = float + double // total 类型是 Double提升规则(Promotion Rules):
运算结果取更大/更精确的类型。
Byte/Short + Int = Int,Int + Long = Long,Float + Double = Double
📝 小练习
题目 1:以下代码能否编译通过?
val a: Long = 100
val b: Int = aA. 可以,Long 可以自动转为 Int
B. 不可以,需要写成 val b: Int = a.toInt()
C. 可以,编译器会智能转换
D. 不可以,Long 无法转换为 Int
【答案】B
【解析】
-
Kotlin 不支持数值类型的隐式转换(no implicit conversion)
-
必须使用显式转换方法
toInt() -
智能转换(Smart Cast)只针对
is类型检查,不适用于数值类型转换 -
Long 可以转为 Int,但必须显式调用
题目 2:以下代码的输出是什么?
fun test(obj: Any) {
val result = obj as? Int ?: -1
println(result)
}
test("Hello")A. Hello
B. null
C. -1
D. 抛出 ClassCastException
【答案】C
【解析】
-
as?是安全转换,"Hello" 不是 Int,所以返回null -
?: -1是 Elvis 操作符,当左侧为null时返回右侧的-1 -
因此最终输出
-1 -
如果使用
as而非as?,则会抛出 ClassCastException
字符与字符串(Characters and Strings)
字符和字符串是日常编程中最常用的类型。Kotlin 对它们的处理既保持了 Java 的兼容性,又提供了更现代、更安全的特性。
Char 类型(Character Type)
Char 表示单个字符,使用单引号包裹。
val letter: Char = 'A'
val digit: Char = '9'
val chinese: Char = '中'
val emoji: Char = '😀' // ⚠️ 部分 emoji 超出 Char 范围"Characters in Kotlin are represented by the type
Char. They cannot be treated directly as numbers." Kotlin 中的字符由Char类型表示,不能直接当作数字处理。
与 Java 的区别:
// Java 中可以这样(隐式转换)
// char c = 65; ✅ Java OK
// Kotlin 中不行
val c: Char = 65 // ❌ 编译错误:Type mismatch
val c2: Char = 65.toChar() // ✅ 必须显式转换Char 的常用操作:
val ch = 'A'
// 字符与数字转换
ch.code // 65(获取 Unicode 码点)
ch.digitToInt() // 用于数字字符 '0'-'9'
// 字符运算(Character arithmetic)
ch + 1 // 'B'(Char + Int = Char)
ch - 'A' // 0(Char - Char = Int)
'Z' - 'A' // 25
// 判断方法
ch.isLetter() // true
ch.isDigit() // false
ch.isUpperCase() // true
ch.lowercaseChar() // 'a'转义字符(Escape Characters):
| 转义序列 | 含义 |
|---|---|
| \t | 制表符(Tab) |
| \n | 换行符(Newline) |
| \r | 回车符(Carriage return) |
| ' | 单引号 |
| " | 双引号 |
| \ | 反斜杠 |
| $ | 美元符号(字符串模板中) |
| \uXXXX | Unicode 字符 |
val tab = '\t'
val newline = '\n'
val heart = '\u2665' // ♥String 不可变性(String Immutability)
Kotlin 的 String 与 Java 一样是不可变的(immutable)。
"Strings are immutable. Once you initialize a string, you can't change its value or assign a new value to it." 字符串是不可变的。一旦初始化,就不能更改其值。
var str = "Hello"
str = "World" // ✅ 变量指向新字符串(变量可变)
str[0] = 'X' // ❌ 不能修改字符串内容(对象不可变)┌─────────────────────────────────────────────────────────────┐
│ String 不可变性图解 │
├─────────────────────────────────────────────────────────────┤
│ │
│ var str = "Hello" │
│ │
│ ┌─────┐ ┌─────────────────┐ │
│ │ str │ ──────► │ "Hello" │ String 对象 │
│ └─────┘ └─────────────────┘ │
│ │
│ str = "World" // 重新赋值 │
│ │
│ ┌─────┐ ┌─────────────────┐ │
│ │ str │ ──────► │ "World" │ 新的 String 对象 │
│ └─────┘ └─────────────────┘ │
│ ┌─────────────────┐ │
│ │ "Hello" │ 旧对象等待 GC │
│ └─────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────┘字符串拼接(String Concatenation):
val s1 = "Hello"
val s2 = "World"
// 方式1:+ 操作符
val s3 = s1 + " " + s2 // "Hello World"
// 方式2:字符串模板(推荐,稍后详解)
val s4 = "$s1 $s2"
// 方式3:StringBuilder(大量拼接时高效)
val sb = StringBuilder()
sb.append(s1).append(" ").append(s2)
val s5 = sb.toString()访问字符串中的字符:
val str = "Kotlin"
str[0] // 'K'(通过索引访问)
str.first() // 'K'
str.last() // 'n'
str.length // 6
// 遍历字符串
for (ch in str) {
println(ch)
}字符串模板(String Templates)
Kotlin 提供强大的 字符串模板(String Template) 功能,使用 $ 符号嵌入变量或表达式。
"String literals may contain template expressions — pieces of code that are evaluated and whose results are concatenated into the string." 字符串字面量可以包含模板表达式——这些代码会被求值,结果会被拼接到字符串中。
val name = "Kotlin"
val version = 1.9
// 简单变量引用
val msg1 = "Language: $name" // "Language: Kotlin"
// 复杂表达式用 ${}
val msg2 = "Length: ${name.length}" // "Length: 6"
val msg3 = "Next version: ${version + 0.1}" // "Next version: 2.0"
// 对象属性
data class User(val name: String, val age: Int)
val user = User("Alice", 25)
val msg4 = "User: ${user.name}, Age: ${user.age}"何时需要大括号(When to use braces):
val price = 100
"$price" // ✅ "100"
"$price dollars" // ✅ "100 dollars"
"$pricedollars" // ❌ 找不到变量 pricedollars
"${price}dollars" // ✅ "100dollars"
// 表达式必须用大括号
"Total: ${price * 1.1}" // ✅
"Array: ${listOf(1,2,3)}" // ✅
"Condition: ${if (price > 50) "expensive" else "cheap"}" // ✅模板中使用美元符号:
val amount = 50
println("Price: \$amount") // 输出: Price: $amount(转义)
println("Price: ${'$'}$amount") // 输出: Price: $50(模板中嵌入 $)原始字符串与多行字符串(Raw Strings / Multi-line Strings)
使用三引号 """ 创建原始字符串(Raw String),可以包含换行和任意字符,无需转义。
// 普通字符串(需要转义)
val escaped = "Line 1\nLine 2\n\tIndented"
// 原始字符串(所见即所得)
val raw = """
Line 1
Line 2
Indented
"""trimMargin() 和 trimIndent():
// 默认会保留前导空格,通常不是我们想要的
val ugly = """
Hello
World
"""
println(ugly)
// 输出:
//
// Hello
// World
//
// 使用 trimIndent() 去除公共缩进
val clean = """
Hello
World
""".trimIndent()
println(clean)
// 输出:
// Hello
// World
// 使用 trimMargin() 自定义边界符
val custom = """
|First line
|Second line
| Indented line
""".trimMargin() // 默认边界符是 |
println(custom)
// 输出:
// First line
// Second line
// Indented line
// 可以指定其他边界符
val other = """
#Line 1
#Line 2
""".trimMargin("#")┌─────────────────────────────────────────────────────────────┐
│ trimMargin 工作原理 │
├─────────────────────────────────────────────────────────────┤
│ │
│ 原始字符串: 处理后: │
│ ┌──────────────────┐ ┌──────────────────┐ │
│ │ |Hello │ │Hello │ │
│ │ | World │ ───► │ World │ │
│ │ |Kotlin │ │Kotlin │ │
│ └──────────────────┘ └──────────────────┘ │
│ ↑ │
│ 删除 | 及之前的空格 │
│ │
└─────────────────────────────────────────────────────────────┘实际应用场景(Use Cases):
// SQL 查询
val sql = """
SELECT *
FROM users
WHERE age > 18
ORDER BY name
""".trimIndent()
// JSON 数据
val json = """
{
"name": "Alice",
"age": 25
}
""".trimIndent()
// 正则表达式(不需要双重转义)
val regex = """\d{3}-\d{4}""".toRegex() // 普通字符串需要 "\\d{3}-\\d{4}"
// HTML 模板
val html = """
<!DOCTYPE html>
<html>
<body>
<h1>$title</h1>
<p>${content}</p>
</body>
</html>
""".trimIndent()📝 小练习
题目 1:以下代码输出什么?
val x = 10
val y = 20
println("Sum: ${x + y}, Product: $x * $y")A. Sum: 30, Product: 200
B. Sum: 30, Product: 10 * 20
C. Sum: ${x + y}, Product: $x * $y
D. 编译错误
【答案】B
【解析】
-
${x + y}是表达式模板,会计算得到30 -
y中,$x被替换为10,$y被替换为20 -
但
*只是普通字符,不会进行乘法运算 -
要计算乘积需要写成
${x * y} -
因此输出
Sum: 30, Product: 10 * 20
题目 2:关于 Kotlin 字符串,以下说法正确的是?
A. String 是可变的,可以直接修改某个位置的字符
B. 原始字符串中不能使用字符串模板 $
C. Char 类型可以直接赋值为整数,如 val c: Char = 65
D. trimMargin() 用于移除多行字符串中指定的边界标记
【答案】D
【解析】
-
A 错误:String 是 immutable,不能修改内容
-
B 错误:原始字符串中完全可以使用模板
"$name" -
C 错误:Kotlin 不支持隐式转换,必须
65.toChar() -
D 正确:
trimMargin()移除每行开头到边界符(默认|)的内容 ✅
布尔类型(Boolean Type)
布尔类型表示逻辑值,只有两个可能的值:true 和 false。
Boolean 基础(Boolean Basics)
val isKotlinFun: Boolean = true
val isJavaObsolete: Boolean = false
// 类型推断
val learning = true // 自动推断为 Boolean"The Boolean type represents boolean objects that can have two values:
trueandfalse." Boolean 类型表示只能有两个值的布尔对象:true和false。
可空布尔值(Nullable Boolean):
val b: Boolean? = null
// 可空布尔值在条件判断中需要显式处理
if (b == true) {
println("b is true")
} else {
println("b is false or null")
}
// 常见模式
b ?: false // 如果 b 为 null,返回 false
b == true // 安全比较,null 也会返回 false逻辑运算符(Logical Operators)
val a = true
val b = false
a && b // false(AND)
a || b // true(OR)
!a // false(NOT)
!b // true
// 复合表达式
(a && !b) || (!a && b) // true(异或的实现)真值表(Truth Table):
┌─────────────────────────────────────────────────┐
│ 逻辑运算真值表 │
├───────┬───────┬─────────┬──────────┬────────────┤
│ A │ B │ A && B │ A || B │ !A │
├───────┼───────┼─────────┼──────────┼────────────┤
│ true │ true │ true │ true │ false │
│ true │ false │ false │ true │ false │
│ false │ true │ false │ true │ true │
│ false │ false │ false │ false │ true │
└───────┴───────┴─────────┴──────────┴────────────┘中缀函数形式(Infix Functions):
val a = true
val b = false
a and b // false(等价于 a && b,但不短路)
a or b // true(等价于 a || b,但不短路)
a xor b // true(异或,无 && || 对应符号)短路求值(Short-circuit Evaluation)
&& 和 || 具有 短路求值(lazy evaluation) 特性:
"The
||and&&operators are lazy, meaning they evaluate the right-hand operand only if necessary."||和&&是惰性的,只有在必要时才会求值右侧操作数。
fun checkFirst(): Boolean {
println("Checking first...")
return true
}
fun checkSecond(): Boolean {
println("Checking second...")
return false
}
// && 短路:第一个为 false 时,不执行第二个
false && checkSecond() // 不会打印 "Checking second..."
// || 短路:第一个为 true 时,不执行第二个
true || checkSecond() // 不会打印 "Checking second..."短路求值流程图:
┌─────────────────────────────────────────────────────────────┐
│ && 短路求值 │
├─────────────────────────────────────────────────────────────┤
│ │
│ A && B │
│ │ │
│ ▼ │
│ ┌────────┐ │
│ │ 求值 A │ │
│ └────┬───┘ │
│ │ │
│ ┌───┴───┐ │
│ ▼ ▼ │
│ false true │
│ │ │ │
│ ▼ ▼ │
│ 返回 ┌────────┐ │
│ false │ 求值 B │ │
│ └────┬───┘ │
│ │ │
│ ▼ │
│ 返回 B │
│ │
└─────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────┐
│ || 短路求值 │
├─────────────────────────────────────────────────────────────┤
│ │
│ A || B │
│ │ │
│ ▼ │
│ ┌────────┐ │
│ └────┬───┘ │
│ │ │
│ ┌───┴───┐ │
│ ▼ ▼ │
│ true false │
│ │ │ │
│ ▼ ▼ │
│ 返回 ┌────────┐ │
│ true │ 求值 B │ │
│ └────┬───┘ │
│ │ │
│ ▼ │
│ 返回 B │
│ │
└─────────────────────────────────────────────────────────────┘短路求值的实际应用(Practical Uses):
// 1. 空安全检查
val list: List<String>? = null
if (list != null && list.isNotEmpty()) { // 安全:list 为 null 时不会调用 isNotEmpty()
println(list.first())
}
// 2. 避免除零错误
val divisor = 0
if (divisor != 0 && 100 / divisor > 10) { // 安全:divisor 为 0 时不会执行除法
println("Greater than 10")
}
// 3. 默认值模式
fun getUser(): User? = null
val user = getUser()
val name = user?.name ?: "Anonymous" // 类似的短路逻辑
// 4. 条件初始化
val expensive = false
val result = expensive && computeExpensiveValue() // expensive 为 false 时不计算短路 vs 非短路(Short-circuit vs Non-short-circuit):
var counter = 0
fun increment(): Boolean {
counter++
return true
}
// 短路操作符
true || increment() // counter = 0(未执行 increment)
false && increment() // counter = 0(未执行 increment)
// 非短路操作符
true or increment() // counter = 1(执行了 increment)
false and increment() // counter = 2(执行了 increment)布尔值在条件语句中的使用
val isLoggedIn = true
val isAdmin = false
// if 表达式
val status = if (isLoggedIn) "Welcome" else "Please login"
// when 表达式
val access = when {
isAdmin -> "Full access"
isLoggedIn -> "Limited access"
else -> "No access"
}
// 直接在条件中使用(推荐简洁写法)
if (isLoggedIn) { /* ... */ } // ✅ 好
if (isLoggedIn == true) { /* ... */ } // ⚠️ 冗余(除非是 Boolean?)📝 小练习
题目 1:以下代码的输出是什么?
var x = 0
val result = false && (++x > 0)
println("x = $x, result = $result")A. x = 1, result = false
B. x = 0, result = false
C. x = 1, result = true
D. x = 0, result = true
【答案】B
【解析】
-
&&是短路运算符 -
左侧
false已经决定了结果必定为false -
因此右侧
(++x > 0)根本不会被执行 -
x保持为0,result为false
题目 2:关于可空布尔值 Boolean?,以下代码安全的是?
val flag: Boolean? = nullA. if (flag) { ... }
B. if (flag == true) { ... }
C. if (!flag) { ... }
D. if (flag && true) { ... }
【答案】B
【解析】
-
A 错误:
Boolean?不能直接用于条件判断,编译器会报错 -
B 正确:
flag == true安全比较,当flag为null时返回false✅ -
C 错误:
!flag对于可空类型不能直接使用!操作符 -
D 错误:
flag && true同样不能对可空类型直接使用&&
正确处理可空布尔值的方式:
if (flag == true) { ... } // 检查是否为 true
if (flag != false) { ... } // 检查是否非 false(null 也通过)
if (flag ?: false) { ... } // null 时当作 false数组(Arrays)
数组是 固定大小(fixed-size) 的元素集合,在 Kotlin 中由 Array 类表示。
"An array is a data structure that holds a fixed number of values of the same type." 数组是一种数据结构,保存固定数量的同类型值。
Array 的创建(Creating Arrays)
Kotlin 提供多种创建数组的方式:
方式一:arrayOf() 函数
// 直接指定元素
val numbers = arrayOf(1, 2, 3, 4, 5)
val strings = arrayOf("a", "b", "c")
val mixed = arrayOf(1, "two", 3.0) // Array<Any>(混合类型)方式二:Array 构造函数
// Array(size) { 初始化表达式 }
val squares = Array(5) { i -> i * i }
// 结果: [0, 1, 4, 9, 16]
val zeros = Array(10) { 0 }
// 结果: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
val indexed = Array(5) { "Item $it" }
// 结果: ["Item 0", "Item 1", "Item 2", "Item 3", "Item 4"]┌─────────────────────────────────────────────────────────────┐
│ Array(5) { i -> i * i } 执行过程 │
├─────────────────────────────────────────────────────────────┤
│ │
│ 索引 i: 0 1 2 3 4 │
│ ↓ ↓ ↓ ↓ ↓ │
│ i * i: 0 1 4 9 16 │
│ ↓ ↓ ↓ ↓ ↓ │
│ ┌─────┬─────┬─────┬─────┬─────┐ │
│ 结果: │ 0 │ 1 │ 4 │ 9 │ 16 │ │
│ └─────┴─────┴─────┴─────┴─────┘ │
│ [0] [1] [2] [3] [4] │
│ │
└─────────────────────────────────────────────────────────────┘方式三:arrayOfNulls() 创建可空数组
val nullableArray: Array<String?> = arrayOfNulls(5)
// 结果: [null, null, null, null, null]
nullableArray[0] = "First"
// 结果: ["First", null, null, null, null]方式四:emptyArray() 创建空数组
val empty: Array<Int> = emptyArray()
println(empty.size) // 0基本类型数组(Primitive Type Arrays)
为了避免装箱开销(boxing overhead),Kotlin 提供了专门的基本类型数组类:
| 类型 | 创建函数 | JVM 对应 |
|---|---|---|
| IntArray | intArrayOf() | int[] |
| LongArray | longArrayOf() | long[] |
| ShortArray | shortArrayOf() | short[] |
| ByteArray | byteArrayOf() | byte[] |
| FloatArray | floatArrayOf() | float[] |
| DoubleArray | doubleArrayOf() | double[] |
| CharArray | charArrayOf() | char[] |
| BooleanArray | booleanArrayOf() | boolean[] |
// 基本类型数组 - 无装箱,性能更好
val intArray: IntArray = intArrayOf(1, 2, 3, 4, 5)
val doubleArray: DoubleArray = doubleArrayOf(1.0, 2.0, 3.0)
val boolArray: BooleanArray = booleanArrayOf(true, false, true)
// 构造函数方式
val sizes = IntArray(5) // [0, 0, 0, 0, 0](默认值为 0)
val evens = IntArray(5) { it * 2 } // [0, 2, 4, 6, 8]Array<Int> vs IntArray 区别:
┌─────────────────────────────────────────────────────────────┐
│ Array<Int> vs IntArray 内存布局 │
├─────────────────────────────────────────────────────────────┤
│ │
│ Array<Int>(装箱数组 / Boxed Array) │
│ ┌─────────────────────────────────────────────┐ │
│ │ ref │ ref │ ref │ ref │ ref │ │ │
│ └──┬────┬────┬────┬────┬──────────────────────┘ │
│ ↓ ↓ ↓ ↓ ↓ │
│ ┌────┐┌────┐┌────┐┌────┐┌────┐ ← Integer 对象(堆上) │
│ │ 1 ││ 2 ││ 3 ││ 4 ││ 5 │ │
│ └────┘└────┘└────┘└────┘└────┘ │
│ │
│ IntArray(原始数组 / Primitive Array) │
│ ┌─────────────────────────────────────────────┐ │
│ │ 1 │ 2 │ 3 │ 4 │ 5 │ │ ← 直接值 │
│ └─────────────────────────────────────────────┘ │
│ │
│ 内存占用: IntArray << Array<Int> │
│ 访问速度: IntArray >> Array<Int> │
│ │
└─────────────────────────────────────────────────────────────┘Best Practice: 处理大量数值数据时,优先使用
IntArray、DoubleArray等基本类型数组。
数组的访问与修改(Accessing and Modifying)
val arr = arrayOf("A", "B", "C", "D", "E")
// 读取元素(Accessing elements)
arr[0] // "A"(索引访问)
arr.get(0) // "A"(方法访问,等价于 arr[0])
arr.first() // "A"(第一个元素)
arr.last() // "E"(最后一个元素)
arr.getOrNull(10) // null(越界时返回 null,不抛异常)
arr.getOrElse(10) { "Default" } // "Default"
// 修改元素(Modifying elements)
arr[0] = "Z" // 直接赋值
arr.set(1, "Y") // 方法方式(等价于 arr[1] = "Y")
// 数组属性
arr.size // 5
arr.indices // 0..4(索引范围)
arr.lastIndex // 4(最后一个索引)常用操作(Common Operations):
val numbers = intArrayOf(3, 1, 4, 1, 5, 9, 2, 6)
// 遍历(Iteration)
for (num in numbers) { println(num) }
for ((index, value) in numbers.withIndex()) {
println("$index: $value")
}
numbers.forEach { println(it) }
numbers.forEachIndexed { i, v -> println("$i: $v") }
// 搜索(Searching)
numbers.contains(5) // true
5 in numbers // true(等价写法)
numbers.indexOf(1) // 1(第一次出现的位置)
numbers.lastIndexOf(1) // 3(最后一次出现的位置)
// 转换(Transformation)
numbers.sorted() // 返回新的排序列表 [1, 1, 2, 3, 4, 5, 6, 9]
numbers.sortedDescending() // 降序
numbers.reversed() // 反转
numbers.distinct() // 去重 [3, 1, 4, 5, 9, 2, 6]
// 聚合(Aggregation)
numbers.sum() // 31
numbers.average() // 3.875
numbers.max() // 9
numbers.min() // 1
numbers.count() // 8
// 过滤(Filtering)
numbers.filter { it > 3 } // [4, 5, 9, 6]
// 映射(Mapping)
numbers.map { it * 2 } // [6, 2, 8, 2, 10, 18, 4, 12]多维数组(Multi-dimensional Arrays)
Kotlin 通过"数组的数组"(Array of Arrays)实现多维数组:
// 二维数组(2D Array)
val matrix: Array<IntArray> = arrayOf(
intArrayOf(1, 2, 3),
intArrayOf(4, 5, 6),
intArrayOf(7, 8, 9)
)
// 访问元素
matrix[0][0] // 1(第一行第一列)
matrix[1][2] // 6(第二行第三列)
matrix[2][1] // 8(第三行第二列)
// 遍历二维数组
for (row in matrix) {
for (cell in row) {
print("$cell ")
}
println()
}┌─────────────────────────────────────────────────────────────┐
│ 二维数组内存结构 │
├─────────────────────────────────────────────────────────────┤
│ │
│ matrix: Array<IntArray> │
│ │
│ ┌───────────────────┐ │
│ │ matrix[0] ────────┼───► [1, 2, 3] │
│ ├───────────────────┤ │
│ │ matrix[1] ────────┼───► [4, 5, 6] │
│ ├───────────────────┤ │
│ │ matrix[2] ────────┼───► [7, 8, 9] │
│ └───────────────────┘ │
│ │
│ 逻辑视图: │
│ col 0 col 1 col 2 │
│ row 0 [ 1 , 2 , 3 ] │
│ row 1 [ 4 , 5 , 6 ] │
│ row 2 [ 7 , 8 , 9 ] │
│ │
│ matrix[row][col] 访问 │
│ │
└─────────────────────────────────────────────────────────────┘动态创建二维数组:
// 创建 3x4 的矩阵,所有元素初始化为 0
val rows = 3
val cols = 4
val grid = Array(rows) { IntArray(cols) }
// 创建并初始化
val board = Array(3) { row ->
IntArray(3) { col -> row * 3 + col + 1 }
}
// 结果:
// [1, 2, 3]
// [4, 5, 6]
// [7, 8, 9]不规则数组(Jagged Array):
// 每行长度可以不同
val jagged = arrayOf(
intArrayOf(1),
intArrayOf(2, 3),
intArrayOf(4, 5, 6),
intArrayOf(7, 8, 9, 10)
)
// 结构:
// [1]
// [2, 3]
// [4, 5, 6]
// [7, 8, 9, 10]数组与集合的转换(Array-Collection Conversion)
val array = arrayOf(1, 2, 3)
// 数组 → List
val list: List<Int> = array.toList()
val mutableList: MutableList<Int> = array.toMutableList()
// 数组 → Set
val set: Set<Int> = array.toSet()
// List → 数组
val backToArray: Array<Int> = list.toTypedArray()
val intArray: IntArray = list.toIntArray() // 基本类型数组📝 小练习
题目 1:以下代码的输出是什么?
val arr = Array(4) { it * it }
println(arr.contentToString())A. [0, 1, 2, 3]
B. [1, 4, 9, 16]
C. [0, 1, 4, 9]
D. [1, 2, 4, 8]
【答案】C
【解析】
-
Array(4) { it * it }创建大小为 4 的数组 -
it从 0 开始:0*0=0,1*1=1,2*2=4,3*3=9 -
结果是
[0, 1, 4, 9] -
注意 B 选项是从 1 开始计算的,不正确
题目 2:关于 IntArray 和 Array<Int> 的区别,以下说法正确的是?
A. 两者完全等价,可以互相赋值
B. IntArray 在 JVM 上编译为 Integer[]
C. Array<Int> 性能优于 IntArray
D. IntArray 避免了装箱开销,性能更好
【答案】D
【解析】
-
A 错误:两者是不同类型,不能直接互相赋值
-
B 错误:
IntArray编译为int[](原始类型数组),Array<Int>才编译为Integer[] -
C 错误:
IntArray性能优于Array<Int>,因为避免了装箱 -
D 正确:
IntArray存储原始值,无需装箱/拆箱,内存占用少,访问速度快 ✅
空类型(Nullable Types)
Kotlin 的 空安全(Null Safety) 是其最重要的特性之一,在类型系统层面杜绝空指针异常(NullPointerException)。
"Kotlin's type system is aimed at eliminating the danger of null references, also known as The Billion Dollar Mistake." Kotlin 的类型系统旨在消除空引用的危险,空引用也被称为"十亿美元错误"。
可空类型与非空类型(Nullable vs Non-null Types)
在 Kotlin 中,默认所有类型都是非空的(non-null by default):
var name: String = "Kotlin"
name = null // ❌ 编译错误:Null cannot be a value of a non-null type String
var age: Int = 25
age = null // ❌ 编译错误要允许 null 值,必须显式声明可空类型(Nullable Type),在类型后加 ?:
var name: String? = "Kotlin"
name = null // ✅ 可空类型可以赋值为 null
var age: Int? = 25
age = null // ✅ OK┌─────────────────────────────────────────────────────────────┐
│ 非空类型 vs 可空类型 │
├─────────────────────────────────────────────────────────────┤
│ │
│ String (非空类型) │
│ ┌─────────────────────────────────────────┐ │
│ │ "Hello" │ "World" │ "" │ "..." │ │
│ └─────────────────────────────────────────┘ │
│ 所有合法字符串值(不包含 null) │
│ │
│ String? (可空类型) │
│ ┌─────────────────────────────────────────┬───────┐ │
│ │ "Hello" │ "World" │ "" │ "..." │ null │ │
│ └─────────────────────────────────────────┴───────┘ │
│ 所有合法字符串值 + null │
│ │
│ 类型关系: String 是 String? 的子类型 │
│ (String is a subtype of String?) │
│ │
└─────────────────────────────────────────────────────────────┘可空类型的使用限制(Restrictions on Nullable Types)
可空类型不能直接调用方法或访问属性:
val name: String? = "Kotlin"
// 直接调用会报错
name.length // ❌ 编译错误:Only safe (?.) or non-null asserted (!!.)
// calls are allowed on a nullable receiver
// 必须处理可能为 null 的情况处理可空类型的四种方式:
方式一:空值检查(Null Check)+ 智能转换
val name: String? = getName()
if (name != null) {
// 在这个分支中,name 被智能转换为 String(非空)
println(name.length) // ✅ 可以直接调用
}
// 另一种写法
if (name == null) {
println("Name is null")
return
}
println(name.length) // ✅ 智能转换生效方式二:安全调用操作符(Safe Call Operator)?.
val name: String? = null
// 如果 name 不为 null,调用 length;否则返回 null
val len: Int? = name?.length // null
val name2: String? = "Kotlin"
val len2: Int? = name2?.length // 6
// 链式安全调用
val city: String? = user?.address?.city┌─────────────────────────────────────────────────────────────┐
│ 安全调用 ?. 工作流程 │
├─────────────────────────────────────────────────────────────┤
│ │
│ name?.length │
│ │ │
│ ▼ │
│ ┌────────────────┐ │
│ │ name == null? │ │
│ └───────┬────────┘ │
│ │ │
│ ┌────┴────┐ │
│ ▼ ▼ │
│ [YES] [NO] │
│ │ │ │
│ ▼ ▼ │
│ null name.length │
│ │ │ │
│ └────┬────┘ │
│ ▼ │
│ 结果: Int? │
│ │
└─────────────────────────────────────────────────────────────┘方式三:Elvis 操作符(Elvis Operator)?:
val name: String? = null
// 如果 name 为 null,使用默认值
val displayName: String = name ?: "Anonymous" // "Anonymous"
// 组合使用
val len: Int = name?.length ?: 0 // 0
// 可以在右侧抛出异常或 return
val nonNullName = name ?: throw IllegalArgumentException("Name required")
val nonNullName2 = name ?: return // 提前返回方式四:非空断言操作符(Non-null Assertion)!!
val name: String? = "Kotlin"
// 断言 name 不为 null,如果为 null 则抛出 NullPointerException
val len: Int = name!!.length // 6
val nullName: String? = null
nullName!!.length // 💥 抛出 NullPointerException⚠️ 警告(Warning):
!!是不安全的,应尽量避免使用。如果你发现代码中有很多!!,这通常是设计问题的信号。 "The!!operator is for NullPointerException lovers."
空值字面量 null(The Null Literal)
null 是一个特殊的字面量,表示"没有值"或"缺失值":
val nothing: String? = null
// null 本身的类型是 Nothing?
val n = null // 类型推断为 Nothing?
// null 可以赋给任何可空类型
val a: Int? = null
val b: String? = null
val c: List<Int>? = nullnull 的比较:
val a: String? = null
val b: String? = null
a == null // true(结构相等判断)
a === null // true(引用相等判断)
a == b // true(两个 null 相等)类型之间的关系(Type Relationships)
// 非空类型可以赋给可空类型
val nonNull: String = "Hello"
val nullable: String? = nonNull // ✅ OK
// 可空类型不能直接赋给非空类型
val nullable2: String? = "World"
val nonNull2: String = nullable2 // ❌ 编译错误
// 必须处理空值
val nonNull3: String = nullable2 ?: "Default" // ✅ OK
val nonNull4: String = nullable2!! // ✅ OK(危险)
if (nullable2 != null) {
val nonNull5: String = nullable2 // ✅ 智能转换
}安全调用与 let 的组合(Safe Call with let)
?.let 是处理可空值的常见模式:
val name: String? = "Kotlin"
// 只有当 name 不为 null 时才执行 let 块
name?.let {
println("Name is $it") // it 是非空的 String
println("Length is ${it.length}")
}
// 等价于
if (name != null) {
println("Name is $name")
println("Length is ${name.length}")
}
// 实际应用:处理可空返回值
getUserOrNull()?.let { user ->
sendEmail(user.email)
logActivity(user.id)
}集合中的空安全(Null Safety in Collections)
// List<String> - 列表非空,元素也非空
val list1: List<String> = listOf("a", "b", "c")
// List<String?> - 列表非空,但元素可空
val list2: List<String?> = listOf("a", null, "c")
// List<String>? - 列表可空,元素非空
val list3: List<String>? = null
// List<String?>? - 列表可空,元素也可空
val list4: List<String?>? = listOf("a", null)┌─────────────────────────────────────────────────────────────┐
│ 集合空安全的四种组合 │
├─────────────────────────────────────────────────────────────┤
│ │
│ List<String> │
│ ┌─────────────────────────────────────┐ │
│ │ "a" │ "b" │ "c" │ "d" │ ← 所有元素非空 │
│ └─────────────────────────────────────┘ │
│ │
│ List<String?> │
│ ┌─────────────────────────────────────┐ │
│ │ "a" │ null │ "c" │ null │ ← 元素可为空 │
│ └─────────────────────────────────────┘ │
│ │
│ List<String>? │
│ ┌─────────────────────────────────────┐ │
│ │ "a" │ "b" │ "c" │ "d" │ 或 null │
│ └─────────────────────────────────────┘ ↑ │
│ 整个列表可能为空 │
│ │
│ List<String?>? │
│ ┌─────────────────────────────────────┐ │
│ │ "a" │ null │ "c" │ null │ 或 null │
│ └─────────────────────────────────────┘ ↑ │
│ ↑ 整个列表可能为空 │
│ 元素可为空 │
│ │
└─────────────────────────────────────────────────────────────┘过滤空值(Filtering Nulls):
val listWithNulls: List<String?> = listOf("a", null, "b", null, "c")
// filterNotNull() 返回 List<String>(非空列表)
val nonNullList: List<String> = listWithNulls.filterNotNull()
// 结果: ["a", "b", "c"]平台类型(Platform Types)
与 Java 互操作时,Kotlin 无法确定 Java 类型的空安全性:
// Java 代码
public class JavaClass {
public String getName() { return null; }
}
// Kotlin 中使用
val name = javaClass.getName() // 类型是 String!(平台类型)平台类型(Platform Type)
String!表示"可能是 String 也可能是 String?",编译器不会强制检查。
处理建议:
// 方式1:假设可空,安全处理
val name: String? = javaClass.getName()
// 方式2:假设非空(如果为 null 会崩溃)
val name: String = javaClass.getName()
// 推荐:明确声明可空性
val name: String = javaClass.getName() ?: "Default"📝 小练习
题目 1:以下代码的输出是什么?
val x: String? = null
val y: String = x?.length?.toString() ?: "unknown"
println(y)A. null
B. "null"
C. "unknown"
D. 抛出 NullPointerException
【答案】C
【解析】
-
x为null,所以x?.length返回null -
null?.toString()返回null -
null ?: "unknown"返回"unknown" -
整个链式调用安全地处理了空值,最终结果是
"unknown"
题目 2:以下哪个代码片段会在编译时报错?
A.
val a: String? = "hello"
val b: String = a ?: ""B.
val a: String? = "hello"
val b: Int = a?.lengthC.
val a: String? = null
val b: Int = a?.length ?: 0D.
val a: String = "hello"
val b: String? = a【答案】B
【解析】
-
A 正确:
a ?: ""返回String类型,可以赋给String -
B 错误:
a?.length返回Int?(因为 a 可能为 null),不能赋给Int❌ -
C 正确:
a?.length ?: 0返回Int类型,可以赋给Int -
D 正确:非空类型可以赋给可空类型
操作符详解(Operators in Depth)
Kotlin 的操作符本质上是函数调用的语法糖(syntactic sugar for function calls)。几乎所有操作符都可以通过 操作符重载(Operator Overloading) 自定义行为。
"Kotlin allows you to provide custom implementations for the predefined set of operators on types." Kotlin 允许你为类型上的预定义操作符提供自定义实现。
算术运算符(Arithmetic Operators)
| 运算符 | 名称 | 对应函数 | 示例 |
|---|---|---|---|
| + | 加法(Addition) | plus() | a + b → a.plus(b) |
| - | 减法(Subtraction) | minus() | a - b → a.minus(b) |
| * | 乘法(Multiplication) | times() | a * b → a.times(b) |
| / | 除法(Division) | div() | a / b → a.div(b) |
| % | 取模(Modulo) | rem() | a % b → a.rem(b) |
val a = 10
val b = 3
a + b // 13
a - b // 7
a * b // 30
a / b // 3(整数除法,向下取整)
a % b // 1(余数)
// 浮点数除法
10.0 / 3.0 // 3.3333...
// 负数取模
-7 % 3 // -1(结果符号与被除数相同)
7 % -3 // 1复合赋值运算符(Augmented Assignments):
var x = 10
x += 5 // x = x + 5 → 15
x -= 3 // x = x - 3 → 12
x *= 2 // x = x * 2 → 24
x /= 4 // x = x / 4 → 6
x %= 4 // x = x % 4 → 2var n = 5
println(++n) // 6(先增后用)
println(n++) // 6(先用后增)
println(n) // 7
val negative = -n // -7比较运算符(Comparison Operators)
| 运算符 | 名称 | 对应函数/实现 |
|---|---|---|
| == | 结构相等(Structural equality) | equals() |
| != | 结构不等 | !equals() |
| === | 引用相等(Referential equality) | 比较内存地址 |
| !== | 引用不等 | |
| < > <= >= | 比较运算 | compareTo() |
// == vs ===
val s1 = "Hello"
val s2 = "Hello"
val s3 = String("Hello".toCharArray())
s1 == s2 // true(内容相同)
s1 === s2 // true(字符串池优化,同一对象)
s1 == s3 // true(内容相同)
s1 === s3 // false(不同对象)┌─────────────────────────────────────────────────────────────┐
│ == vs === 图解 │
├─────────────────────────────────────────────────────────────┤
│ │
│ 字符串池 (String Pool) 堆内存 (Heap) │
│ ┌─────────────────────┐ ┌─────────────────┐ │
│ │ "Hello" │ │ "Hello" │ │
│ └─────────────────────┘ └─────────────────┘ │
│ ↑ ↑ ↑ │
│ │ │ │ │
│ s1 s2 s3 │
│ │
│ s1 == s2 ✅ true (内容相等) │
│ s1 === s2 ✅ true (同一引用) │
│ s1 == s3 ✅ true (内容相等) │
│ s1 === s3 ❌ false (不同引用) │
│ │
└─────────────────────────────────────────────────────────────┘compareTo 与比较运算符:
val a = 5
val b = 10
a < b // true → a.compareTo(b) < 0
a > b // false → a.compareTo(b) > 0
a <= b // true → a.compareTo(b) <= 0
a >= b // false → a.compareTo(b) >= 0
// 字符串比较(按字典序)
"apple" < "banana" // true
"Zoo" < "apple" // true(大写字母 ASCII 值更小)逻辑运算符(Logical Operators)
val a = true
val b = false
a && b // false
a || b // true
!a // false
// 短路示例(已在布尔类型章节详细讨论)
false && expensiveOperation() // 不执行 expensiveOperation
true || expensiveOperation() // 不执行 expensiveOperation位运算符(Bitwise Operators)
| 函数 | 说明 | 等价于(其他语言) |
|---|---|---|
| shl(n) | 左移(Shift Left) | << |
| shr(n) | 有符号右移(Signed Shift Right) | >> |
| ushr(n) | 无符号右移(Unsigned Shift Right) | >>> |
| and(n) | 按位与(Bitwise AND) | & |
| or(n) | 按位或(Bitwise OR) | |
| xor(n) | 按位异或(Bitwise XOR) | ^ |
| inv() | 按位取反(Bitwise Inversion) | ~ |
Kotlin 中位运算使用 命名中缀函数(named infix functions) 而非符号:
val x = 0b1010 // 10
val y = 0b1100 // 12
x and y // 0b1000 = 8
x or y // 0b1110 = 14
x xor y // 0b0110 = 6
x.inv() // ...11110101 = -11(补码表示)
// 移位操作
1 shl 4 // 0b10000 = 16(左移 4 位)
16 shr 2 // 0b00100 = 4(右移 2 位)┌─────────────────────────────────────────────────────────────┐
│ 位运算图解 │
├─────────────────────────────────────────────────────────────┤
│ │
│ x = 0b1010 (10) │
│ y = 0b1100 (12) │
│ │
│ x and y: x or y: x xor y: │
│ 1 0 1 0 1 0 1 0 1 0 1 0 │
│ & 1 1 0 0 | 1 1 0 0 ^ 1 1 0 0 │
│ ───────── ───────── ───────── │
│ 1 0 0 0 1 1 1 0 0 1 1 0 │
│ (8) (14) (6) │
│ │
│ 1 shl 4: │
│ 0000 0001 → 0001 0000 (16) │
│ │
│ 16 shr 2: │
│ 0001 0000 → 0000 0100 (4) │
│ │
└─────────────────────────────────────────────────────────────┘
实际应用场景(Use Cases):
// 1. 标志位操作(Flag operations)
val READ = 0b001 // 1
val WRITE = 0b010 // 2
val EXECUTE = 0b100 // 4
var permissions = READ or WRITE // 0b011 = 3
// 检查权限
val canRead = (permissions and READ) != 0 // true
val canExecute = (permissions and EXECUTE) != 0 // false
// 添加权限
permissions = permissions or EXECUTE // 0b111 = 7
// 移除权限
permissions = permissions and WRITE.inv() // 0b101 = 5
// 2. 快速乘除 2 的幂
val n = 5
n shl 1 // 10(相当于 n * 2)
n shl 3 // 40(相当于 n * 8)
16 shr 1 // 8(相当于 16 / 2)
// 3. 交换两个变量(不用临时变量)
var a = 5
var b = 3
a = a xor b
b = a xor b
a = a xor b
// 现在 a = 3, b = 5in 与 !in 操作符(Membership Operators)
in 用于检查元素是否 包含在(contained in) 某个范围或集合中:
| 运算符 | 对应函数 | 说明 |
|---|---|---|
| a in b | b.contains(a) | a 是否在 b 中 |
| a !in b | !b.contains(a) | a 是否不在 b 中 |
// 在范围中检查
val x = 5
x in 1..10 // true(1 ≤ x ≤ 10)
x in 1 until 5 // false(1 ≤ x < 5)
x !in 10..20 // true(x 不在 10-20 之间)
// 在集合中检查
val list = listOf("apple", "banana", "cherry")
"banana" in list // true
"grape" !in list // true
// 在字符串中检查
'e' in "Hello" // true
"ell" in "Hello" // true(子字符串检查)
// 在 Map 中检查键
val map = mapOf("a" to 1, "b" to 2)
"a" in map // true(检查键是否存在)在 when 和 if 中使用:
val score = 85
val grade = when (score) {
in 90..100 -> "A"
in 80 until 90 -> "B"
in 70 until 80 -> "C"
in 60 until 70 -> "D"
else -> "F"
}
// grade = "B"
if (score in 0..100) {
println("Valid score")
}区间操作符(Range Operators)
Kotlin 提供了强大的 区间(Range) 支持:
| 表达式 | 类型 | 含义 |
|---|---|---|
| a..b | ClosedRange | a ≤ x ≤ b(闭区间) |
| a until b | IntRange | a ≤ x < b(半开区间) |
| a downTo b | IntProgression | a ≥ x ≥ b(递减) |
| a..b step n | IntProgression | 步长为 n |
// 基本区间
val range1 = 1..5 // 1, 2, 3, 4, 5
val range2 = 1 until 5 // 1, 2, 3, 4(不包含 5)
val range3 = 5 downTo 1 // 5, 4, 3, 2, 1
// 带步长
val range4 = 1..10 step 2 // 1, 3, 5, 7, 9
val range5 = 10 downTo 1 step 3 // 10, 7, 4, 1
// 遍历区间
for (i in 1..5) println(i) // 1 2 3 4 5
for (i in 5 downTo 1) println(i) // 5 4 3 2 1
for (i in 1 until 5 step 2) println(i) // 1 3┌─────────────────────────────────────────────────────────────┐
│ 区间类型图解 │
├─────────────────────────────────────────────────────────────┤
│ │
│ 1..5 (闭区间 / Closed Range) │
│ ●────●────●────●────● │
│ 1 2 3 4 5 │
│ │
│ 1 until 5 (半开区间 / Half-open Range) │
│ ●────●────●────●────○ │
│ 1 2 3 4 5 │
│ ↑ 不包含 │
│ │
│ 5 downTo 1 (递减区间 / Descending Range) │
│ ●────●────●────●────● │
│ 5 4 3 2 1 │
│ ←──────────────────── │
│ │
│ 1..10 step 2 (步长区间 / Stepped Range) │
│ ●─────────●─────────●─────────●─────────● │
│ 1 3 5 7 9 │
│ │
└─────────────────────────────────────────────────────────────┘字符区间(Character Ranges):
val letters = 'a'..'z'
val digits = '0'..'9'
'c' in letters // true
'5' in digits // true
for (ch in 'A'..'E') print(ch) // ABCDE区间常用操作:
val range = 1..100
range.first // 1
range.last // 100
range.count() // 100
range.contains(50) // true
range.isEmpty() // false
// 转换为列表
range.toList() // [1, 2, 3, ..., 100]
// 反转
(1..5).reversed() // [5, 4, 3, 2, 1]Elvis 操作符(Elvis Operator)?:
Elvis 操作符是处理 可空值(nullable values) 的简洁方式:
a ?: b— 如果a不为 null,返回a;否则返回b
val name: String? = null
// 基本用法:提供默认值
val displayName = name ?: "Unknown" // "Unknown"
// 链式使用
val result = first() ?: second() ?: third() ?: "default"
// 与安全调用组合
val length = name?.length ?: 0Elvis 的右侧可以是表达式:
val name: String? = null
// 抛出异常
val nonNull = name ?: throw IllegalArgumentException("Name required")
// 提前返回
fun process(name: String?) {
val n = name ?: return
println("Processing: $n")
}
// 执行代码块
val result = name ?: run {
println("Name was null, calculating default...")
calculateDefaultName()
}┌─────────────────────────────────────────────────────────────┐
│ Elvis 操作符工作流程 │
├─────────────────────────────────────────────────────────────┤
│ │
│ a ?: b │
│ │ │
│ ▼ │
│ ┌──────────────┐ │
│ │ a == null? │ │
│ └──────┬───────┘ │
│ │ │
│ ┌────┴────┐ │
│ ▼ ▼ │
│ [YES] [NO] │
│ │ │ │
│ ▼ ▼ │
│ 返回 b 返回 a │
│ │
│ 注意: 只有当 a 为 null 时才会计算 b │
│ (Elvis is also short-circuit) │
│ │
└─────────────────────────────────────────────────────────────┘为什么叫 Elvis?
┌─────────────────────────┐
│ │
│ ?: 像不像 Elvis │
│ ↓ 的发型? 😎 │
│ 眼睛 头发 │
│ │
└─────────────────────────┘操作符优先级(Operator Precedence)
从高到低的优先级(Higher to Lower):
┌──────────────────────────────────────────────────────────────┐
│ 操作符优先级表 │
├──────────────────────────────────────────────────────────────┤
│ 优先级 │ 操作符 │
├──────────────────────────────────────────────────────────────┤
│ 最高 │ 后缀: ++, --, ., ?., () │
│ │ 前缀: -, +, ++, --, !, 标签 │
│ │ 类型: as, as? │
│ │ 乘除: *, /, % │
│ │ 加减: +, - │
│ │ 范围: .. │
│ │ 中缀: 自定义中缀函数 │
│ │ Elvis: ?: │
│ │ 命名检查: in, !in, is, !is │
│ │ 比较: <, >, <=, >= │
│ │ 相等: ==, !=, ===, !== │
│ │ 与: && │
│ │ 或: || │
│ 最低 │ 赋值: =, +=, -=, *=, /=, %= │
└──────────────────────────────────────────────────────────────┘// 优先级示例
val result = 1 + 2 * 3 // 7(先乘后加)
val result2 = (1 + 2) * 3 // 9(括号改变优先级)
val x = 1..5 step 2 // .. 先于 step
val check = 5 in 1..10 && true // in 先于 &&
val value: Int? = null
val r = value ?: 0 + 10 // ?: 优先级低于 +,等于 value ?: (0 + 10) = 10
val r2 = (value ?: 0) + 10 // 需要括号才能先执行 Elvis📝 小练习
题目 1:以下代码的输出是什么?
val x = 10
val y = 3
println("${x / y}, ${x % y}, ${x.toDouble() / y}")A. 3, 1, 3.0
B. 3, 1, 3.3333333333333335
C. 3.33, 1, 3.33
D. 3, 1, 3.33
【答案】B
【解析】
-
x / y=10 / 3= 3(整数除法,向下取整) -
x % y=10 % 3= 1(余数) -
x.toDouble() / y=10.0 / 3= 3.3333...(浮点数除法) -
所以输出
3, 1, 3.3333333333333335
题目 2:以下哪个表达式的值是 true?
A. 5 in 1 until 5
B. "abc" === "abc"
C. 10 shl 1 == 5
D. (null ?: "a") == "a"
【答案】D
【解析】
-
A 错误:
1 until 5是[1, 5),不包含 5,所以5 in 1 until 5是false -
B 不确定:字符串字面量可能在字符串池中,
===可能是true,但这取决于实现,不应依赖 -
C 错误:
10 shl 1= 20,20 == 5是false -
D 正确:
null ?: "a"返回"a","a" == "a"是true✅
知识点速查表(Quick Reference)
┌─────────────────────────────────────────────────────────────────────────┐
│ 基础语法与类型 速查表 │
├─────────────────────────────────────────────────────────────────────────┤
│ │
│ 【变量声明】 │
│ val name = "Kotlin" // 不可变(immutable reference) │
│ var count = 0 // 可变(mutable reference) │
│ lateinit var repo: Repo // 延迟初始化(late initialization) │
│ const val MAX = 100 // 编译时常量(compile-time constant) │
│ │
│ 【类型层次】 │
│ Any → 所有非空类型的根(root of non-null types) │
│ Any? → 所有类型的根(root of all types) │
│ Nothing → 底部类型(bottom type, subtype of everything) │
│ Unit → 无返回值(like void, but is a real type) │
│ │
│ 【数值类型】 │
│ 整数: Byte(8) < Short(16) < Int(32) < Long(64) │
│ 浮点: Float(32) < Double(64) │
│ 字面量: 100, 100L, 0xFF, 0b1010, 1_000_000, 3.14, 3.14f │
│ │
│ 【类型转换】 │
│ 显式: toInt(), toLong(), toDouble()...(no implicit conversion) │
│ 智能: if (x is String) x.length(smart cast after type check) │
│ 安全: x as? String(returns null if failed) │
│ │
│ 【字符串】 │
│ 不可变: String 内容不可修改(immutable) │
│ 模板: "Hello, $name" 或 "Sum: ${a + b}" │
│ 原始: """多行\n无需转义""" │
│ 处理: trimIndent(), trimMargin() │
│ │
│ 【数组】 │
│ 泛型: Array<T>,使用 arrayOf() │
│ 原始: IntArray, DoubleArray...(无装箱开销) │
│ 创建: Array(5) { it * 2 } │
│ 访问: arr[0], arr.first(), arr.getOrNull(i) │
│ │
│ 【空安全】 │
│ 可空声明: var name: String? = null │
│ 安全调用: name?.length(返回 Int?) │
│ Elvis: name ?: "default"(提供默认值) │
│ 非空断言: name!!(危险,可能 NPE) │
│ │
│ 【操作符】 │
│ 算术: + - * / % │
│ 比较: == != === !== < > <= >= │
│ 逻辑: && || !(短路求值) │
│ 位运算: shl shr ushr and or xor inv │
│ 范围: .. until downTo step │
│ 成员: in !in │
│ │
└─────────────────────────────────────────────────────────────────────────┘核心概念思维导图(Mind Map)
┌─────────────────────┐
│ Kotlin 基础语法 │
│ 与类型系统 │
└──────────┬──────────┘
│
┌───────────────┬───────────┼───────────┬───────────────┐
│ │ │ │ │
▼ ▼ ▼ ▼ ▼
┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐
│ 变量 │ │ 类型 │ │ 空安全 │ │ 数组 │ │ 操作符 │
└────┬────┘ └────┬────┘ └────┬────┘ └────┬────┘ └────┬────┘
│ │ │ │ │
┌────┴────┐ ┌────┴────┐ ┌────┴────┐ ┌────┴────┐ ┌────┴────┐
│val/var │ │ 基本类型 │ │ T?声明 │ │ Array │ │ 算术/比较│
│lateinit │ │ 引用类型 │ │ ?.调用 │ │ IntArray│ │ 逻辑/位 │
│const │ │ 类型层次 │ │ ?: Elvis│ │多维数组 │ │ 范围/in │
└─────────┘ │ 类型转换 │ │ !! 断言 │ └─────────┘ └─────────┘
└─────────┘ └─────────┘与 Java 的主要区别(Key Differences from Java)
| 特性 | Java | Kotlin |
|---|---|---|
| 变量声明 | 类型在前 int x = 1 | 类型在后 val x: Int = 1 |
| 空安全 | 运行时 NPE | 编译时检查,类型区分 |
| 数值转换 | 隐式扩展 int → long | 必须显式 toInt() |
| 字符串 | "x = " + x | "x = $x" 模板 |
| 原始类型 | int vs Integer | 统一 Int,编译器优化 |
| 类型推断 | Java 10+ var | 全面类型推断 |