主题
第 3 章 基本语法(val/var、类型、字符串模板、控制流)
学习目标:能熟练定义变量、看懂 Kotlin 类型系统、用字符串模板优雅地拼字符串、用
if/when/for/while写出比 Java 更简洁的控制流。本章学完,你就能写"小工具脚本"了。
3.1 变量:val 与 var 的世纪之争
3.1.1 一句话区别
kotlin
val name = "Alice" // val = value,只读引用,类似 Java 的 final
var age = 25 // var = variable,可重新赋值
age = 26 // ✅ OK
name = "Bob" // ❌ 编译报错:Val cannot be reassigned📌 生活化类比:
val像「身份证号」:办下来就不变了var像「手机号」:今天用 138 明天可以换 139
3.1.2 val 的"误解":它真的不变吗?
很多 Java 转过来的人会以为 val 等于 final,导致一个常见的"翻车":
kotlin
val list = mutableListOf(1, 2, 3)
list.add(4) // ✅ 可以!list 还是同一个 list,只是内部多了一个元素
list = listOf(1) // ❌ 不行!val 禁止重新赋值val 只是说**「这个变量名永远指向同一个对象」,但「对象内部能不能改」是对象自己的事**。
📌 类比:
val是说「你不能换房子」,但「家具能不能换」要看房子本身(mutable / immutable)。
3.1.3 为啥默认要用 val?
kotlin
// 例子:一个并发场景的 bug
var counter = 0
List(100) { Thread { counter++ } }.forEach { it.start() }
// 多线程都在改 counter,最终值不一定是 100,可能是 87、93……(race condition)
// 改成 val + 不可变思维
val counter = AtomicInteger(0)
// 强制你用线程安全的容器好处:
- 避免意外修改:90% 的变量其实根本不需要改
- 更易并发:不可变数据天然线程安全
- 代码可读:一眼看出哪些是"状态"哪些是"常量"
最佳实践:默认全部写 val,编译器报错"必须能重新赋值"时再改成 var。
3.1.4 类型推断 vs 显式类型
kotlin
val name = "Alice" // 编译器推断为 String
val age = 25 // 推断为 Int
val pi = 3.14 // 推断为 Double(不是 Float!)
val isActive = true // 推断为 Boolean
// 显式声明
val name: String = "Alice"
val age: Int = 25💡 什么时候必须显式写类型?
- 公开 API(库的对外函数返回值):明确签名,避免悄悄变更
- 接口属性:必须声明类型
- 延迟初始化:
var name: String? = null,编译器猜不出来
3.1.5 常量:top-level + const
kotlin
const val MAX_RETRY = 3 // 编译期常量,必须是 String/原始类型,必须放在 top-level 或 object
const val APP_NAME = "MyApp"
// const 不能用在函数里、不能修饰 List 等对象类型
const val list = listOf(1, 2, 3) // ❌const val 跟普通 val 的区别:const 是编译期就确定值,编译后会被内联到调用点(更快)。普通 val 至少要等到运行时初始化。
3.2 基本类型
3.2.1 数字类型
┌─────────────────────────────────────────────────────┐
│ Byte 8 bit -128 ~ 127 │
│ Short 16 bit -32768 ~ 32767 │
│ Int 32 bit -21亿 ~ 21亿 │
│ Long 64 bit 超大整数 │
│ Float 32 bit 单精度浮点 │
│ Double 64 bit 双精度浮点(默认浮点类型) │
└─────────────────────────────────────────────────────┘kotlin
val b: Byte = 127
val s: Short = 32767
val i: Int = 1_000_000 // 下划线增加可读性
val l: Long = 1_000_000L // 注意 L 后缀
val f: Float = 3.14f // 注意 f 后缀
val d: Double = 3.14
val hex: Int = 0xFF // 16 进制
val bin: Int = 0b1010 // 2 进制3.2.2 数字之间不会自动转换!⚠️
这是 Java 转 Kotlin 最容易踩的坑:
kotlin
val i: Int = 100
val l: Long = i // ❌ 编译报错:Type mismatch
val l: Long = i.toLong() // ✅ 必须显式转换kotlin
// Java 里这样能跑:
int i = 100;
long l = i; // 自动从 int 升 long
// Kotlin 强制你写明白:
val i: Int = 100
val l: Long = i.toLong()📌 为啥 Kotlin 这么"啰嗦"?因为隐式转换是 bug 的温床。比如
int * int在 Java 里会溢出,Kotlin 要求你明确写i.toLong() * j才能避免。
类型转换函数
每个数字类型都有 toByte()、toShort()、toInt()、toLong()、toFloat()、toDouble()、toChar():
kotlin
val i = 65
val c = i.toChar() // 'A'
val s = "123"
val n = s.toInt() // 1233.2.3 字符 Char
kotlin
val c: Char = 'A'
val newline: Char = '\n'
// Char 跟 Int 不能直接相加(Java 可以)
val next = c + 1 // ✅ Char + Int = Char,结果是 'B'
val n: Int = c // ❌ 编译报错
val n: Int = c.code // ✅ 显式获取 ASCII 码 653.2.4 布尔 Boolean
kotlin
val ok: Boolean = true
val notOk = !ok
val and = ok && true
val or = ok || false3.2.5 字符串 String
字符串本身没啥特别的(不可变、UTF-16),但 Kotlin 有两个非常好用的特性:
字符串模板(重点)
kotlin
val name = "Alice"
val age = 25
// 简单插值用 $
println("Hello, $name!") // Hello, Alice!
// 表达式插值用 ${...}
println("$name is ${age + 1} next year")
println("name length = ${name.length}")
// 想输出 $ 字面量
println("Price: \$100") // Price: $100
// 多行字符串(三个引号)
val sql = """
SELECT id, name
FROM users
WHERE age > $age
""".trimIndent()📌
trimIndent()会自动把每行的公共前导空白删掉,特别适合写 SQL / JSON / HTML。
字符串方法精选
kotlin
"hello".uppercase() // "HELLO"
" hi ".trim() // "hi"
"a,b,c".split(",") // ["a", "b", "c"]
"abc".reversed() // "cba"
"hello".replace("l", "L") // "heLLo"
"hello world".substring(6) // "world"
"hello".isBlank() // false
" ".isBlank() // true
"abc".repeat(3) // "abcabcabc"
// 字符串遍历
for (c in "abc") println(c)3.3 控制流:比 Java 更"语句即表达式"
Kotlin 的控制流跟 Java 看起来很像,但有一个重要区别:几乎所有控制流都是表达式(有返回值),可以直接赋值。
3.3.1 if 是个表达式
kotlin
// Java 风格(Kotlin 也支持)
val max: Int
if (a > b) max = a else max = b
// Kotlin 风格 —— if 直接当表达式
val max = if (a > b) a else b
// 多分支
val grade = if (score >= 90) {
"A"
} else if (score >= 80) {
"B"
} else {
"C"
}
// 注意:用 if 表达式时必须要有 else,不然编译器没法保证有值💡 取代三元运算符:Kotlin 没有
a ? b : c,因为if (a) b else c已经更清楚了。
3.3.2 when:超级版 switch
when 是 Kotlin 最优雅的控制流之一,把 Java 的 switch 摁在地上摩擦。
基本用法
kotlin
val x = 3
when (x) {
1 -> println("one")
2 -> println("two")
3 -> println("three")
else -> println("other")
}多个值合并
kotlin
when (x) {
1, 3, 5, 7, 9 -> println("奇数")
2, 4, 6, 8 -> println("偶数")
}范围、类型检查
kotlin
when (x) {
in 0..9 -> println("个位数")
in 10..99 -> println("两位数")
!in 0..99 -> println("不是两位数以内")
}
fun describe(obj: Any): String = when (obj) {
is Int -> "整数 ${obj}" // 自动 smart cast
is String -> "字符串 长度 ${obj.length}"
is List<*> -> "列表 ${obj.size} 个元素"
else -> "未知类型"
}when 当表达式(无参数)
kotlin
val day = 3
val result = when {
day in 1..5 -> "工作日"
day == 6 || day == 7 -> "周末"
else -> "不是有效日期"
}📌 跟 Java switch 的差别:
- 没有 break(自动 break)
- 可以是表达式,有返回值
- 支持范围、类型、任意条件判断
- 类型检查后自动 smart cast,不用手动
(String) obj
3.3.3 for:遍历一切
Kotlin 的 for 只有"遍历"形式,没有 C 风格的 for(int i=0; i<n; i++)。
kotlin
// 遍历范围
for (i in 1..5) println(i) // 1 2 3 4 5
for (i in 1 until 5) println(i) // 1 2 3 4(左闭右开)
for (i in 5 downTo 1) println(i) // 5 4 3 2 1
for (i in 1..10 step 2) println(i) // 1 3 5 7 9
// 遍历集合
for (item in listOf("a", "b", "c")) println(item)
// 带索引
for ((index, value) in listOf("a","b","c").withIndex()) {
println("$index: $value")
}
// 遍历 Map
val map = mapOf("a" to 1, "b" to 2)
for ((k, v) in map) println("$k = $v")📌
..范围运算符:1..5创建一个IntRange,可以遍历也可以做if (x in 1..5)检查。
3.3.4 while 与 do-while
kotlin
var i = 0
while (i < 5) {
println(i)
i++
}
do {
val input = readLine()
println("got: $input")
} while (input != "quit")3.3.5 break / continue 与标签
kotlin
loop@ for (i in 1..3) {
for (j in 1..3) {
if (i == 2 && j == 2) break@loop // 跳出外层 loop
println("$i, $j")
}
}3.4 范围(Range):Kotlin 的小心机
kotlin
val r1 = 1..10 // IntRange [1, 10]
val r2 = 1 until 10 // IntRange [1, 10)
val r3 = 10 downTo 1 // IntRange [10..1]
val r4 = 1..10 step 2 // IntProgression 1, 3, 5, 7, 9
// 检查是否在范围内
if (x in 1..100) { ... }
if (x !in 1..100) { ... }
// 遍历
for (i in 1..10) println(i)
// 字符范围
for (c in 'a'..'z') println(c)
// 倒序
for (i in 10 downTo 1 step 3) println(i) // 10, 7, 4, 1📌
in操作符:万能的"包含"检查,集合、范围、字符串都能用:kotlin"hello" in listOf("hi", "hello", "hey") // true 'a' in "abc" // true 5 in 1..10 // true
3.5 表达式 vs 语句
Kotlin 跟 Java 一个本质区别:几乎所有东西都是表达式(有返回值)。
| 控制流 | Java | Kotlin |
|---|---|---|
if | 语句(无返回值) | 表达式(可以赋值) |
when / switch | 语句 | 表达式 |
try/catch | 语句 | 表达式 |
for | 语句 | 语句(唯一例外) |
while | 语句 | 语句 |
kotlin
// try 也是表达式
val number: Int = try {
str.toInt()
} catch (e: NumberFormatException) {
0
}
// 函数体可以直接是表达式
fun max(a: Int, b: Int): Int = if (a > b) a else b3.6 类型检查与转换:is / as
3.6.1 is 检查 + Smart Cast(自动转型)
kotlin
fun describe(obj: Any) {
if (obj is String) {
// 在这个分支里 obj 自动当 String 用,不用手动 (String) obj
println("String of length ${obj.length}")
}
}📌 Smart Cast 触发条件:
- 变量是
val(不可变,编译器才敢做这个推断)- 中间没有可能改变 obj 的代码
- 不是开放类的属性(多线程可能改)
3.6.2 as 强制转换
kotlin
val s: String = obj as String // 转不成抛 ClassCastException
val s: String? = obj as? String // 安全转换:转不成返回 null3.7 一个综合小例子:成绩等级判定
kotlin
fun grade(score: Int): String = when {
score !in 0..100 -> "无效分数"
score >= 90 -> "A"
score >= 80 -> "B"
score >= 70 -> "C"
score >= 60 -> "D"
else -> "F"
}
fun main() {
val scores = listOf(95, 82, 73, 60, 45, 105, -5)
for ((i, s) in scores.withIndex()) {
println("第 ${i + 1} 个分数 $s -> ${grade(s)}")
}
}输出:
第 1 个分数 95 -> A
第 2 个分数 82 -> B
第 3 个分数 73 -> C
第 4 个分数 60 -> D
第 5 个分数 45 -> F
第 6 个分数 105 -> 无效分数
第 7 个分数 -5 -> 无效分数只用了 6 行的判定函数 + 7 行的 main,做了完整功能。这就是 Kotlin。
3.8 章末小结
★ 第 3 章核心知识图谱 ★
│
┌───────────────────────┼───────────────────────┐
│ │ │
┌──▼──┐ ┌───▼────┐ ┌───▼────┐
│ 变量 │ │ 类型 │ │ 控制流 │
├─────┤ ├────────┤ ├────────┤
│val │ │Int Long│ │if 表达式│
│var │ │Float │ │when 表达│
│const│ │String │ │for in │
│ │ │类型推断│ │range │
│ │ │不自动转│ │is/as │
└─────┘ └────────┘ └────────┘完成本章你应该:
- ✅ 知道
val/var/const val的差别 - ✅ 能列出 6 种数字类型并说出各自范围
- ✅ 能用
$和${}写字符串模板 - ✅ 能用
when替代多分支if-else - ✅ 能用
1..10和1 until 10写循环 - ✅ 知道
if/when/try都是表达式
🎤 3.9 章末面试题(10 道)
Q1. val 和 var 的区别是什么?编译后变成什么?
答:
val:只读引用(reference is final),类似 Java 的final变量。只能赋值一次。var:可变引用,能重新赋值。
编译后:
val→ Java 的private final字段 +getter(无 setter)var→ Java 的private字段 +getter+setter
注意:val 不代表对象不可变 —— val list = mutableListOf<Int>() 后,list 这个引用不能换,但 list.add(1) 是允许的。
Q2. val 和 const val 有什么区别?
答:
| 维度 | val | const val |
|---|---|---|
| 初始化时机 | 运行时 | 编译期 |
| 必须是字面量 | 不是 | 是(基本类型 / String) |
| 必须放 top-level / object | 否 | 是(不能放函数里) |
| 编译产物 | 字段 + getter | 直接被内联到调用点 |
| 反射能否拿到 | 是 | 是,但内联后调用方可能根本不查找 |
kotlin
const val MAX = 100 // ✅ 编译期常量
val PI = Math.PI // ✅ 运行时初始化
const val LIST = listOf(1) // ❌ List 不是基本类型
fun foo() {
const val x = 1 // ❌ 不能放函数里
}Q3. Kotlin 为什么不允许数字类型隐式转换?
答:避免隐藏 bug。
- Java 允许
int → long隐式转换,但int * int仍可能溢出 - Kotlin 强制
i.toLong() * j,让程序员主动思考精度问题
💡 回避陷阱:
val a: Long = 1是 OK 的(因为1是字面量,编译器知道这是 Long 字面值);但val i: Int = 1; val a: Long = i就报错。
Q4. 字符串模板里 $x 和 ${x.length} 有啥区别?什么时候必须用花括号?
答:
$variableName:只能引用单个变量名${expression}:可以放任意 Kotlin 表达式
必须用 ${} 的场景:
- 访问对象成员:
${user.name} - 调用方法:
${list.size} - 表达式计算:
${a + b} - 想紧跟字母数字下划线(避免歧义):
"price_${id}_total"而不是"price_$id_total"(后者会把$id_total当成一个变量名)
Q5. if 在 Kotlin 里跟 Java 有啥不同?
答:Kotlin 的 if 是表达式,有返回值,可以直接赋值给变量:
kotlin
val max = if (a > b) a else b重要约束:作为表达式用时,必须有 else 分支(编译器需要保证所有路径都有返回值)。
Java 的 if 是语句,没返回值,必须用三元 ?: 或额外变量。Kotlin 干脆把三元运算符砍了,因为 if (a) b else c 已经够清楚。
Q6. when 和 Java 的 switch 有什么区别?
答:
| 维度 | Java switch | Kotlin when |
|---|---|---|
| 是否表达式 | 否(Java 14+ switch 表达式才支持) | 是 |
| 是否需要 break | 需要(不写就 fallthrough) | 不需要(自动) |
| 支持类型 | byte/short/int/char/String/enum | 任意类型 |
| 支持范围 | 不支持 | in 1..10 |
| 支持类型检查 | 不支持 | is String 并自动 smart cast |
| 无参数模式 | 不支持 | when { x > 0 -> ... }(替代多分支 if-else) |
Q7. Kotlin 的 for 循环为什么没有 C 风格 for (i=0; i<n; i++)?
答:设计哲学是「只保留一种最清晰的写法」。
- 想遍历范围用
for (i in 0 until n) - 想倒序用
for (i in n-1 downTo 0) - 想步长用
for (i in 0..n step 2) - 想带索引用
for ((i, v) in list.withIndex())
更清晰、更少 bug(C 风格 for 容易写错边界条件 < 还是 <=)。
Q8. in 操作符可以用在哪些地方?
答:
- 范围检查:
x in 1..10 - 集合包含:
name in listOf("Alice", "Bob") - 字符串包含:
'a' in "abc" - map 键检查:
"key" in map - for 循环:
for (i in 1..10) - 取反:
!in
底层是看对象有没有定义 contains 操作符。
Q9. 什么是 Smart Cast?什么时候 Smart Cast 不工作?
答:在 is 检查后,编译器自动把变量当对应类型用,省去手动转型:
kotlin
fun foo(obj: Any) {
if (obj is String) {
println(obj.length) // 自动当 String 用
}
}不工作的场景:
var变量(值可能在中间被改)—— 改用val- 开放类的属性(多线程可能改)—— 用本地
val复制一份 - 可空类型
?.let { }后嵌套深层 —— 用requireNotNull(x)帮编译器明确
kotlin
class C { var name: String? = null }
fun foo(c: C) {
if (c.name != null) {
println(c.name.length) // ❌ 不能 smart cast,name 是 var
}
val name = c.name // ✅ 复制到本地 val
if (name != null) {
println(name.length) // ✅ OK
}
}Q10. try 是表达式,能怎么用?
答:
kotlin
val n: Int = try {
str.toInt()
} catch (e: NumberFormatException) {
-1
}
// 如果 try 块抛异常被 catch 捕获,整个表达式的值就是 catch 块返回的值finally 块不影响表达式的返回值,它只用来清理资源(关闭文件等)。
kotlin
val r = try { /* throw */ "A" } catch (e: Exception) { "B" } finally { println("cleanup") }
// r == "B",但 cleanup 也会打印下一章 → 第 4 章 · 函数与 Lambda →
🎬 可视化演示
演示加载缓慢或样式异常?点此在新标签页打开 ↗
💻 示例代码
kotlin
/**
* 第 3 章 · 基本语法 — 综合示例
*
* 涵盖:
* - val / var / const val
* - 数字类型与显式转换
* - 字符串模板(含三引号字符串)
* - if 表达式
* - when 全场景(值匹配、范围、类型、无参)
* - for / while / 范围
* - is / as 类型检查
*
* 运行:kotlinc BasicSyntax.kt -include-runtime -d bs.jar && java -jar bs.jar
*/
const val APP_NAME = "Kotlin Demo" // 编译期常量
const val MAX_SCORE = 100
fun main() {
println("=== 1. 变量 ===")
val name = "Alice" // 类型推断为 String
var age = 25 // var 可变
age += 1
val score: Double = 88.5 // 显式类型
println("$name, $age 岁,分数 $score")
println("\n=== 2. 数字类型 ===")
val big: Long = 1_000_000_000L
val pi: Double = 3.14159
val hex: Int = 0xFF
println("big=$big, pi=$pi, hex=$hex (十进制 $hex)")
val i = 100
val l: Long = i.toLong() // 必须显式转换
println("Int $i 转 Long = $l")
println("\n=== 3. 字符串模板 ===")
println("Hello, $name!")
println("$name 长度 = ${name.length}")
println("一年后 ${age + 1} 岁")
val sql = """
SELECT id, name, score
FROM students
WHERE score >= $MAX_SCORE * 0.6
""".trimIndent()
println("---- 多行字符串 ----")
println(sql)
println("\n=== 4. if 表达式 ===")
val a = 10; val b = 20
val max = if (a > b) a else b
println("max($a, $b) = $max")
println("\n=== 5. when 全场景 ===")
listOf(0, 5, 50, 999, -10).forEach { x ->
val cat = when (x) {
0 -> "零"
in 1..9 -> "个位数"
in 10..99 -> "两位数"
in 100..999 -> "三位数"
!in 0..999 -> "超出范围"
else -> "其他"
}
println("$x -> $cat")
}
// when 当类型派发
listOf<Any>(1, "hello", 3.14, listOf(1,2,3)).forEach {
println("${it.javaClass.simpleName}: ${describe(it)}")
}
// when 无参数模式
val score2 = 78
val grade = when {
score2 !in 0..100 -> "无效"
score2 >= 90 -> "A"
score2 >= 80 -> "B"
score2 >= 70 -> "C"
score2 >= 60 -> "D"
else -> "F"
}
println("分数 $score2 -> 等级 $grade")
println("\n=== 6. for 循环 ===")
println("升序 1..5:")
for (i in 1..5) print("$i ")
println()
println("半开区间 1 until 5:")
for (i in 1 until 5) print("$i ")
println()
println("倒序 5 downTo 1:")
for (i in 5 downTo 1) print("$i ")
println()
println("步长 1..10 step 2:")
for (i in 1..10 step 2) print("$i ")
println()
println("带索引:")
val fruits = listOf("apple", "banana", "cherry")
for ((idx, fruit) in fruits.withIndex()) {
println(" $idx -> $fruit")
}
println("\n=== 7. is / as 类型检查 ===")
val obj: Any = "hello world"
if (obj is String) {
// smart cast: 自动把 obj 当 String 用
println("是 String,长度 ${obj.length}")
}
val any: Any = 123
val maybeStr: String? = any as? String // 安全转换
println("any as? String = $maybeStr") // null
println("\n=== 8. try 表达式 ===")
val parsed: Int = try { "42a".toInt() } catch (e: NumberFormatException) { -1 }
println("解析失败时返回 = $parsed")
}
fun describe(obj: Any): String = when (obj) {
is Int -> "整数 $obj,平方是 ${obj * obj}"
is String -> "字符串 \"$obj\",长度 ${obj.length}"
is Double -> "浮点数 $obj,向下取整 ${obj.toInt()}"
is List<*> -> "列表,含 ${obj.size} 个元素:$obj"
else -> "未知类型 ${obj::class.simpleName}"
}kotlin
/**
* 第 3 章 · 实战案例 — 学生成绩等级判定器
*
* 综合演示了:
* - when 表达式
* - 范围 (in 0..100)
* - 字符串模板
* - for + withIndex
* - 函数定义
*
* 业务场景:教务系统给一批学生算成绩等级。
*/
data class Student(val name: String, val score: Int)
fun grade(score: Int): String = when {
score !in 0..100 -> "无效分数"
score >= 90 -> "A"
score >= 80 -> "B"
score >= 70 -> "C"
score >= 60 -> "D"
else -> "F"
}
fun comment(score: Int): String = when (score) {
in 90..100 -> "优秀!继续保持 🎉"
in 80..89 -> "良好,下次冲一冲 💪"
in 70..79 -> "中等,还有提升空间"
in 60..69 -> "刚及格,要加把劲!"
in 0..59 -> "不及格,需要补课 😢"
else -> "分数无效,请检查输入"
}
fun main() {
val students = listOf(
Student("Alice", 95),
Student("Bob", 82),
Student("Charlie", 73),
Student("David", 60),
Student("Eve", 45),
Student("Frank", 105),
)
println("=== 学生成绩单 ===")
println("%-10s %-6s %-6s %s".format("姓名", "分数", "等级", "评语"))
println("-".repeat(50))
for (s in students) {
val g = grade(s.score)
val c = comment(s.score)
println("%-10s %-6d %-6s %s".format(s.name, s.score, g, c))
}
println("\n=== 班级统计 ===")
val valid = students.filter { it.score in 0..100 }
val avg = valid.map { it.score }.average()
val max = valid.maxByOrNull { it.score }
val min = valid.minByOrNull { it.score }
val passed = valid.count { it.score >= 60 }
println("有效人数: ${valid.size}")
println("平均分: ${"%.1f".format(avg)}")
println("最高分: ${max?.name} ${max?.score}")
println("最低分: ${min?.name} ${min?.score}")
println("及格率: ${"%.0f%%".format(passed * 100.0 / valid.size)}")
}