第3章:基础语法速览

快速掌握 Kotlin 核心语法,从变量到函数一网打尽

🔍

本章导师:柯南

核心方法论:真相只有一个,语法规律就是线索

「一门编程语言的语法,就像一起案件的现场。每一个关键字都是一条线索,每一段代码都是一份证据。只要仔细观察、逻辑推理,就能还原出整套规律的真相。各位,跟我一起来破解 Kotlin 语法之谜吧!」

3.1 变量与常量

val vs var — 不可变与可变

让我们从案件的第一条线索开始调查。Kotlin 的变量声明现场只留下了两个「嫌疑人」:valvar。先破解它们的名字密码 —— valvalue(值)的缩写,意味着一旦赋值就不能再改变,强调的是「这是一个确定的值」;varvariable(变量)的缩写,意味着它的值可以随时变化。记住这两个英文原词,就永远不会搞混:value 不变,variable 可变

下面这张对照表就是关键证据:

概念 Kotlin Java C++ Python
不可变变量 val final const / constexpr 无直接等价物
可变变量 var 普通变量 普通变量 普通变量

现在,让我们来验证这条线索。看看实际的代码证据:

// val — 不可变(推荐优先使用)
val name = "Kotlin"
// name = "Java"  // 编译错误!val 不能重新赋值

// var — 可变
var count = 0
count = 1       // OK,var 可以重新赋值
count = 2       // OK

柯南的推理笔记

根据大量代码现场的调查,我得出一个结论:默认使用 val,只在确实需要修改时才用 var。这能让代码更安全、更易于推理,也更适合并发编程。真相只有一个 —— 优先选择不可变!

C++ 开发者对照

val 近似于 C++ 中的 const 变量,var 则是普通变量。但要注意一个关键区别:Kotlin 中没有指针和引用的概念。C++ 里你需要区分 const intconst int*int* constconst int& 等复杂组合,而 Kotlin 只需要 valvar 两个关键字就能搞定。此外,val 只意味着变量引用不可变 —— 如果指向一个可变对象(如 MutableList),对象本身的内容仍然可以修改,类似 C++ 中 T* const ptr(指针本身不可变,但指向的内容可变)。

类型推断

接下来,我注意到一个重要线索:Kotlin 编译器有一项出色的「侦查能力」—— 类型推断。它可以根据赋值自动推断变量类型,不需要你显式声明。这就像我根据现场遗留的证据,就能推理出嫌疑人的身份一样!

// 编译器自动推断类型
val name = "Kotlin"          // 推断为 String
val age = 25                 // 推断为 Int
val pi = 3.14               // 推断为 Double
val isKotlinFun = true       // 推断为 Boolean

从这段代码中,我们能推理出什么?对比 Java,Kotlin 简洁了很多:

// Java — 必须写类型
final String name = "Kotlin";
final int age = 25;

// Kotlin — 类型推断,省去冗余
val name = "Kotlin"
val age = 25

显式类型声明

当然,有时候侦探也需要明确亮出证据。你可以手动指定类型,或者在没有初始值时必须指定 —— 这是 Kotlin 的铁律,不可违反:

// 显式声明类型
val age: Int = 25
val price: Double = 9.99
val greeting: String = "你好"

// 没有初始值时,必须显式声明类型
val result: Int
result = calculateSomething()  // 稍后赋值(仅限一次)

编译时常量:const val

在深入调查中,我发现了一个更严格的「嫌疑人」—— const val。它用于声明编译时常量,类似于 Java 中的 static final。它必须是顶层声明或 object 的成员,且值必须是基本类型或 String。这条线索表明,const val 的值在编译阶段就已经锁定,不可能在运行时改变:

// 编译时常量 — 在编译阶段就确定值
const val MAX_COUNT = 100
const val BASE_URL = "https://api.example.com"
const val PI = 3.14159265

// 对比 Java:
// public static final int MAX_COUNT = 100;
// public static final String BASE_URL = "https://api.example.com";

val vs const val

val 在运行时赋值(可以调用函数得到值),而 const val 在编译时就确定了值。const val 只能用于基本类型和 String。用排除法来记忆:能在编译期确定的用 const val,其余一律用 val

3.2 基本数据类型

数字类型

接下来,我们来调查 Kotlin 数据类型的真相。这里有一条至关重要的线索:Kotlin 中没有 Java 的原始类型(primitive types),一切皆对象。但编译器会在可能的情况下优化为 JVM 原始类型 —— 这就好比嫌疑人表面上伪装成了对象,但在底层却悄悄地以原始类型的身份运行:

// 整数类型
val byte: Byte = 127               // 8 位
val short: Short = 32767           // 16 位
val int: Int = 2147483647          // 32 位
val long: Long = 9999999999L       // 64 位,注意后缀 L

// 浮点数类型
val float: Float = 3.14f           // 32 位,注意后缀 f
val double: Double = 3.14159265    // 64 位(默认)

// 数字字面量中可以用下划线增加可读性
val million = 1_000_000
val hexColor = 0xFF00FF
val binaryByte = 0b11010010
C++ 开发者对照

这是一条关键线索:Kotlin 中一切皆对象,没有 C++ 中 int / Integer(Java 的装箱类型)的区分,也没有原始类型(primitive types)。在 C++ 中你要操心 intlongint32_tsize_t 等不同平台上的宽度差异,而 Kotlin 的 Int 始终是 32 位、Long 始终是 64 位,跨平台一致。此外,Kotlin 的 String 是一等公民对象,不像 C++ 中你需要在 char*const char*std::stringstd::string_view 之间来回选择。Kotlin 中只有一种 String,内置丰富的方法,不需要引入任何头文件。

布尔、字符

// Boolean
val isVisible: Boolean = true
val isEmpty = false

// Char — 单引号
val letter: Char = 'A'
val digit: Char = '9'
val chinese: Char = '中'

字符串与字符串模板

我在代码现场发现了一件「秘密武器」—— Kotlin 的字符串模板。经过推理,我断定这是 Kotlin 最实用的特性之一,它比 Java 的字符串拼接简洁太多了。让我们来收集证据:

val name = "Kotlin"
val version = 2.0

// 简单变量引用 — 用 $
val msg1 = "Hello, $name"                 // "Hello, Kotlin"

// 表达式 — 用 ${}
val msg2 = "$name $version 已发布"         // "Kotlin 2.0 已发布"

val list = listOf("A", "B", "C")
val msg3 = "共 ${list.size} 个元素"       // "共 3 个元素"
val msg4 = "大写: ${name.uppercase()}"   // "大写: KOTLIN"

对比 Java 的字符串拼接 —— 证据确凿,Kotlin 的写法更胜一筹:

// Java — 冗长的字符串拼接
String msg = "Hello, " + name + "! 共 " + list.size() + " 个元素";

// Kotlin — 清晰的字符串模板
val msg = "Hello, $name! 共 ${list.size} 个元素"

Kotlin 还支持多行字符串(raw string),用三个双引号。这条线索在处理 JSON、SQL 等场景时尤其有用:

val json = """
    {
        "name": "$name",
        "version": $version
    }
""".trimIndent()

数组

// 通用数组
val names: Array<String> = arrayOf("Alice", "Bob", "Charlie")
val numbers = arrayOf(1, 2, 3, 4, 5)

// 原始类型数组(性能更好,避免装箱)
val intArr: IntArray = intArrayOf(1, 2, 3)
val doubleArr: DoubleArray = doubleArrayOf(1.0, 2.0)

// 访问元素
println(names[0])      // "Alice"
names[1] = "Bobby"     // 修改元素

类型转换

注意了!这是一条容易让人「翻车」的线索。Kotlin 不会进行隐式类型转换,必须显式调用转换函数。在我看来,这种设计就像严格的法庭规则 —— 每一步操作都必须有明确的依据,不允许任何含糊不清的推理:

val intVal: Int = 42

// 错误!不能隐式转换
// val longVal: Long = intVal   // 编译错误

// 正确 — 显式转换
val longVal: Long = intVal.toLong()
val doubleVal: Double = intVal.toDouble()
val stringVal: String = intVal.toString()

// 字符串转数字
val parsed: Int = "123".toInt()
val safeP: Int? = "abc".toIntOrNull()  // 转换失败返回 null,不会崩溃

柯南的警告

Java 中 int 可以隐式转为 long,C++ 中更是充满了各种隐式转换陷阱(intdouble、窄化转换等),但 Kotlin 中一律不行。忘记显式转换是从 Java 和 C++ 迁移过来时的常见「犯罪现场」。

3.3 控制流

if 是表达式

案件进入了新的阶段 —— 控制流调查。我发现了第一条重要线索:在 Kotlin 中,if 不仅仅是语句,它还是一个表达式,可以有返回值。这意味着你不再需要三元运算符 ? :。用推理来理解:既然 if 能返回值,那三元运算符就成了多余的「嫌疑人」—— 被排除了!

// if 作为表达式 — 替代三元运算符
val a = 10
val b = 20
val max = if (a > b) a else b    // max = 20

// 也可以带代码块
val description = if (a > b) {
    println("a 更大")
    "a wins"    // 最后一个表达式是返回值
} else {
    println("b 更大")
    "b wins"
}

让我把各路「嫌疑人」摆在一起做个对比,这样真相就更清楚了:

// Java — 三元运算符
int max = (a > b) ? a : b;

// Python — 条件表达式
// max = a if a > b else b

// Kotlin — if 表达式(更可读)
val max = if (a > b) a else b

when 表达式

现在,案件迎来了最精彩的转折 —— when 表达式登场!经过细致的排查,我可以确认它是 Kotlin 中替代 switch 的终极利器,功能强大得多。让我们逐步收集证据:

// 基本用法 — 匹配值
val dayOfWeek = 3
val dayName = when (dayOfWeek) {
    1 -> "周一"
    2 -> "周二"
    3 -> "周三"
    4 -> "周四"
    5 -> "周五"
    6, 7 -> "周末"           // 多个值合并
    else -> "未知"            // 默认分支
}

从这段代码中,我们能推理出什么?when 还能匹配范围,这比 switch 灵活多了:

// 匹配范围
val score = 85
val grade = when (score) {
    in 90..100 -> "优秀"
    in 80..89  -> "良好"
    in 70..79  -> "中等"
    in 60..69  -> "及格"
    else       -> "不及格"
}

更令人惊讶的证据 —— when 还能匹配类型,并且自动进行智能转换:

// 匹配类型
fun describe(obj: Any): String = when (obj) {
    is Int    -> "整数: $obj"
    is String -> "字符串,长度 ${obj.length}"  // 智能类型转换!
    is Boolean -> "布尔值: $obj"
    else     -> "未知类型"
}

还有最后一条线索 —— 无参 when,它可以作为 if-else 链的更优雅替代:

// 无参 when — 作为 if-else 链的替代(更优雅)
val temperature = 35
val weather = when {
    temperature < 0   -> "严寒"
    temperature < 10  -> "寒冷"
    temperature < 25  -> "舒适"
    temperature < 35  -> "炎热"
    else              -> "酷暑"
}

柯南的推理总结:when vs switch

经过全面调查,真相已经浮出水面:Kotlin 的 when 不需要 break,不会「穿透」到下一个分支。它也可以匹配范围、类型和任意条件,比 Java 的 switch 和 C++ 的 switch 强大得多。案件到此结束 —— when 就是王者!

for 循环与区间

让我们继续追踪下一条线索 —— for 循环。Kotlin 的 for 循环与区间运算符搭配使用,形成了一套优雅的遍历体系:

// 遍历区间
for (i in 1..5) {
    print("$i ")    // 输出: 1 2 3 4 5
}

// until — 不包含末尾
for (i in 0 until 5) {
    print("$i ")    // 输出: 0 1 2 3 4
}

// downTo — 倒序
for (i in 5 downTo 1) {
    print("$i ")    // 输出: 5 4 3 2 1
}

// step — 指定步长
for (i in 0..10 step 2) {
    print("$i ")    // 输出: 0 2 4 6 8 10
}

遍历集合也留下了清晰的证据 —— Kotlin 提供了多种方式:

// 遍历集合
val fruits = listOf("苹果", "香蕉", "橙子")

for (fruit in fruits) {
    println(fruit)
}

// 使用 indices 获取索引
for (i in fruits.indices) {
    println("$i: ${fruits[i]}")
}

// 使用 withIndex() 同时获取索引和值
for ((index, fruit) in fruits.withIndex()) {
    println("$index: $fruit")
}
// 输出:
// 0: 苹果
// 1: 香蕉
// 2: 橙子

while / do-while

这两个嫌疑人的行为模式和其他语言几乎一致,不需要过多调查:

// while — 先判断后执行
var n = 5
while (n > 0) {
    print("$n ")    // 输出: 5 4 3 2 1
    n--
}

// do-while — 先执行后判断(至少执行一次)
var input: String
do {
    input = readLine() ?: ""
    println("你输入了: $input")
} while (input != "quit")

区间运算总结

将所有区间相关的线索汇总到一张证据表中:

表达式 含义 结果
1..10 闭区间(包含两端) 1, 2, 3, ..., 10
1 until 10 半开区间(不含末尾) 1, 2, 3, ..., 9
10 downTo 1 倒序 10, 9, 8, ..., 1
1..10 step 2 步长为 2 1, 3, 5, 7, 9
10 downTo 1 step 3 倒序 + 步长 10, 7, 4, 1

3.4 函数

基本定义

案件来到了「函数」这一关键现场。经过仔细调查,我发现 Kotlin 的函数定义规则十分清晰 —— 用 fun 关键字开头,参数类型在名称后面,返回类型紧随其后:

// 标准函数定义
fun add(a: Int, b: Int): Int {
    return a + b
}

// 调用
val sum = add(3, 5)  // 8

让我们把各路「嫌疑人」排列对比,真相自然浮现:

// Java
public int add(int a, int b) {
    return a + b;
}

// Python
// def add(a, b):
//     return a + b

// Kotlin
fun add(a: Int, b: Int): Int {
    return a + b
}
C++ 开发者对照

Kotlin 函数有一个重要特征:它们总是属于某个类(成员函数),或者是顶层函数(直接写在文件中,不需要包在类里)。这和 C++ 不同 —— C++ 有自由函数(free functions)、类的成员函数、友元函数等多种形式。Kotlin 的顶层函数编译后实际上会变成一个包含静态方法的 Java 类,但在使用时你完全不需要关心这个细节。此外,Kotlin 的参数类型写在名称后面(a: Int),而 C++ 写在前面(int a),这一点要特别注意。

单表达式函数

我在调查中发现了一条精妙的线索:当函数体只有一个表达式时,可以用 = 简写,返回类型也可以省略。这就是 Kotlin 追求简洁的决定性证据:

// 标准写法
fun add(a: Int, b: Int): Int {
    return a + b
}

// 单表达式简写 — 省去花括号和 return
fun add(a: Int, b: Int) = a + b

// 更多示例
fun isEven(n: Int) = n % 2 == 0
fun greeting(name: String) = "Hello, $name!"
fun max(a: Int, b: Int) = if (a > b) a else b

默认参数

又一条关键线索浮出水面 —— Kotlin 支持给函数参数设置默认值,从而大幅减少方法重载的需要。对一个侦探来说,这就像是找到了一个能替代多条分散线索的核心证据:

fun greet(name: String = "World", punctuation: String = "!") {
    println("Hello, $name$punctuation")
}

greet()                        // Hello, World!
greet("Kotlin")               // Hello, Kotlin!
greet("Kotlin", ".")         // Hello, Kotlin.

对比 Java,不再需要写一堆重载方法了 —— 这是铁证:

// Java — 需要多个重载
void greet() { greet("World"); }
void greet(String name) { greet(name, "!"); }
void greet(String name, String punctuation) {
    System.out.println("Hello, " + name + punctuation);
}

// Kotlin — 一个函数搞定
fun greet(name: String = "World", punctuation: String = "!") {
    println("Hello, $name$punctuation")
}
C++ 开发者对照

C++ 也支持默认参数,写法是 void greet(string name = "World"),但有一些限制:默认参数必须从右往左连续提供,不能跳过中间的参数。而 Kotlin 配合命名参数,可以跳过任意中间参数,只指定你关心的那几个 —— 这比 C++ 灵活得多。此外,C++ 的默认参数值在声明处(头文件)绑定,而 Kotlin 的默认值编译后会生成合成方法来处理。

命名参数

这条线索与默认参数联合使用时威力倍增。调用函数时可以指定参数名,让代码更清晰,也可以跳过中间的默认参数 —— 这是我在侦查中发现的最优雅的搭配组合:

fun createUser(
    name: String,
    age: Int = 0,
    email: String = "",
    isAdmin: Boolean = false
) {
    println("$name, $age, $email, admin=$isAdmin")
}

// 使用命名参数 — 跳过中间参数,代码更易读
createUser(name = "Alice", isAdmin = true)
createUser(name = "Bob", email = "bob@example.com")
createUser(name = "Charlie", age = 30, email = "c@test.com")

可变参数:vararg

fun printAll(vararg messages: String) {
    for (msg in messages) {
        println(msg)
    }
}

printAll("Hello", "World", "Kotlin")

// 展开数组传入 vararg — 使用 * 展开运算符
val words = arrayOf("Hello", "World")
printAll(*words)

Unit 返回类型

Unit 相当于 Java 的 void,表示函数不返回有意义的值。根据我的推理,省略不写是更常见的做法:

// 显式写 Unit
fun logMessage(msg: String): Unit {
    println("[LOG] $msg")
}

// 省略 Unit(推荐)
fun logMessage(msg: String) {
    println("[LOG] $msg")
}

柯南的案件总结:函数篇

经过对函数的全面调查,真相已经大白:Kotlin 的函数默认参数 + 命名参数的组合非常强大,可以替代 Java 中大量的方法重载和 Builder 模式。写代码时优先使用这些特性让 API 更简洁 —— 这就是我作为侦探给出的最终建议。

3.5 与其他语言的对比小贴士

现在让我们把所有收集到的线索汇总到一张「案件证据总表」中。如果你有 Java、C++ 或 Python 经验,下面这张对照表能帮你快速建立映射关系 —— 就像把嫌疑人的指纹与数据库比对一样高效:

特性 Kotlin Java C++ Python
不可变变量 val x = 1 final int x = 1; const int x = 1; 无内置支持
可变变量 var x = 1 int x = 1; int x = 1; x = 1
类型推断 val x = "hi" var x = "hi"; (Java 10+) auto x = "hi"s; (C++11) x = "hi"
Null 安全 var x: String? = null @Nullable String x = null; std::optional<string> (C++17) x = None
字符串模板 "Hello, $name" "Hello, " + name std::format("Hello, {}", name) (C++20) f"Hello, {name}"
类型转换 x.toInt() (int) xInteger.parseInt(x) static_cast<int>(x) int(x)
函数定义 fun add(a: Int, b: Int) = a + b int add(int a, int b) { return a + b; } int add(int a, int b) { return a + b; } def add(a, b): return a + b
默认参数 fun f(x: Int = 0) 不支持(需重载) void f(int x = 0) def f(x=0)
命名参数 f(name = "K") 不支持 不支持(C++20 有指定初始化器) f(name="K")
条件表达式 if (c) a else b c ? a : b c ? a : b a if c else b
分支匹配 when (x) { ... } switch (x) { ... } switch (x) { ... } match x: ... (3.10+)
行尾分号 不需要 必须 必须 不需要

柯南的推理笔记:从 Java 迁移的关键心得

柯南的推理笔记:从 Python 迁移的关键心得

C++ 开发者对照

从 C++ 迁移到 Kotlin,以下几点是最需要注意的「案件现场」:

本章练习

案件的调查告一段落,现在是时候检验你的推理能力了。以下练习将帮助你巩固本章学到的 Kotlin 基础语法线索。

练习 1:变量声明与类型推断 入门

声明以下变量,判断哪些应该用 val,哪些应该用 var

  • 一个人的名字(假设不会改变)
  • 一个人的年龄(每年会变)
  • 圆周率
  • 一个计数器,初始值为 0

对每个变量,让编译器推断类型(不要显式写类型),然后写出编译器会推断出的类型是什么。

提示

想想每个变量在程序运行过程中是否需要被重新赋值。名字和圆周率是固定的,而年龄和计数器需要更新。推断类型时,记住:整数默认是 Int,带小数点的数默认是 Double,双引号括起来的文本是 String

参考答案
val name = "柯南"         // String — 名字不变,用 val
var age = 17              // Int — 年龄会变,用 var
val pi = 3.14159265       // Double — 圆周率是常数,用 val
var counter = 0           // Int — 计数器需要累加,用 var

// 验证:可以修改 var,不能修改 val
age = 18                  // OK
counter = counter + 1     // OK
// name = "新一"           // 编译错误!val 不能重新赋值

练习 2:when 表达式实战 进阶

编写一个函数 evaluateScore,接收一个 Int 类型的分数参数,使用 when 表达式返回对应的评价字符串:

  • 100 分:「满分!完美推理!」
  • 90-99 分:「优秀」
  • 80-89 分:「良好」
  • 60-79 分:「及格」
  • 0-59 分:「不及格」
  • 其他(负数或超过 100):「无效分数」
提示

注意 100 分需要单独匹配(用具体值),而其他区间用 in 关键字加区间运算符 .. 来匹配。考虑使用单表达式函数的写法。匹配顺序很重要 —— 把具体值匹配放在区间匹配前面。

参考答案
fun evaluateScore(score: Int): String = when (score) {
    100           -> "满分!完美推理!"
    in 90..99    -> "优秀"
    in 80..89    -> "良好"
    in 60..79    -> "及格"
    in 0..59     -> "不及格"
    else          -> "无效分数"
}

// 测试
println(evaluateScore(100))  // 满分!完美推理!
println(evaluateScore(85))   // 良好
println(evaluateScore(42))   // 不及格
println(evaluateScore(-1))   // 无效分数

练习 3:字符串模板综合运用 入门

给定以下变量:

val detective = "柯南"
val casesResolved = 312
val successRate = 99.7

用字符串模板写出一句话:「侦探柯南已成功破获 312 起案件,破案率高达 99.7%!」。要求:使用 $${} 两种语法,至少一处使用表达式(如调用方法或做运算)。

提示

简单的变量引用用 $variable,如果需要调用方法或写表达式则用 ${expression}。你可以尝试在模板中嵌入 ${casesResolved + 1} 之类的表达式来展示模板的灵活性,或者使用 ${detective.length} 之类的方法调用。

参考答案
val detective = "柯南"
val casesResolved = 312
val successRate = 99.7

// 基础版
val report = "侦探$detective 已成功破获 $casesResolved 起案件,破案率高达 $successRate%!"
println(report)
// 侦探柯南已成功破获 312 起案件,破案率高达 99.7%!

// 进阶版 — 使用表达式
val report2 = "侦探${detective.uppercase()} 已成功破获 ${casesResolved} 起案件(名字共 ${detective.length} 个字),破案率 $successRate%!"
println(report2)
// 侦探柯南 已成功破获 312 起案件(名字共 2 个字),破案率 99.7%!

练习 4:函数与默认参数 进阶

编写一个函数 formatClue,用于格式化案件线索。函数参数如下:

  • content: String — 线索内容(必填)
  • caseNumber: Int — 案件编号,默认值为 1
  • priority: String — 优先级,默认值为 "普通"
  • isVerified: Boolean — 是否已验证,默认值为 false

函数返回格式化后的字符串:"[案件#编号][优先级] 线索内容 (已验证/未验证)"。然后用命名参数调用这个函数 3 次:只传内容;传内容和优先级;传内容、案件编号和已验证标志。

提示

利用默认参数避免重载。在函数体内用 if 表达式根据 isVerified 生成 "已验证" 或 "未验证" 的文本。调用时使用命名参数来跳过中间参数。

参考答案
fun formatClue(
    content: String,
    caseNumber: Int = 1,
    priority: String = "普通",
    isVerified: Boolean = false
): String {
    val status = if (isVerified) "已验证" else "未验证"
    return "[案件#$caseNumber][$priority] $content ($status)"
}

// 调用 1:只传内容
println(formatClue("现场发现指纹"))
// [案件#1][普通] 现场发现指纹 (未验证)

// 调用 2:传内容和优先级(使用命名参数跳过 caseNumber)
println(formatClue(content = "发现可疑脚印", priority = "紧急"))
// [案件#1][紧急] 发现可疑脚印 (未验证)

// 调用 3:传内容、案件编号和已验证标志(跳过 priority)
println(formatClue(content = "嫌疑人不在场证明", caseNumber = 7, isVerified = true))
// [案件#7][普通] 嫌疑人不在场证明 (已验证)

练习 5:综合推理挑战 挑战

编写一个函数 investigate,接收一个 Any 类型的参数(即任意类型),使用 when 表达式对传入的值进行「调查分析」,返回一段描述字符串:

  • 如果是 Int 且为正数,返回 「正整数线索:值为 X」
  • 如果是 Int 且为 0 或负数,返回 「无效线索:整数 X 不是正数」
  • 如果是 String 且长度大于 0,返回 「文字线索:内容为 "X",共 N 个字符」
  • 如果是 String 且为空,返回 「空白线索:没有内容」
  • 如果是 Boolean 为 true,返回 「确认线索:已核实」
  • 如果是 Boolean 为 false,返回 「否定线索:已排除」
  • 其他类型,返回 「未知线索:类型为 X」(用 ::class.simpleName 获取类型名)
提示

这道题需要组合使用 when 的类型匹配(is)和无参 when 的条件判断。一种方式是先用 is 匹配类型,在匹配后利用智能转换进行额外判断。另一种方式是使用无参 when,在每个分支中同时检查类型和条件。记住,when 匹配到第一个满足的分支就会停止。

参考答案
fun investigate(clue: Any): String = when {
    clue is Int && clue > 0           -> "正整数线索:值为 $clue"
    clue is Int                        -> "无效线索:整数 $clue 不是正数"
    clue is String && clue.isNotEmpty() -> "文字线索:内容为 \"$clue\",共 ${clue.length} 个字符"
    clue is String                     -> "空白线索:没有内容"
    clue == true                       -> "确认线索:已核实"
    clue == false                      -> "否定线索:已排除"
    else                               -> "未知线索:类型为 ${clue::class.simpleName}"
}

// 测试
println(investigate(42))          // 正整数线索:值为 42
println(investigate(-5))          // 无效线索:整数 -5 不是正数
println(investigate("指纹"))      // 文字线索:内容为 "指纹",共 2 个字符
println(investigate(""))          // 空白线索:没有内容
println(investigate(true))        // 确认线索:已核实
println(investigate(false))       // 否定线索:已排除
println(investigate(3.14))        // 未知线索:类型为 Double
← 上一章 目录 下一章 →