Skip to content

第 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)
// 强制你用线程安全的容器

好处

  1. 避免意外修改:90% 的变量其实根本不需要改
  2. 更易并发:不可变数据天然线程安全
  3. 代码可读:一眼看出哪些是"状态"哪些是"常量"

最佳实践:默认全部写 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()    // 123

3.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 码 65

3.2.4 布尔 Boolean

kotlin
val ok: Boolean = true
val notOk = !ok
val and = ok && true
val or = ok || false

3.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 的差别

  1. 没有 break(自动 break)
  2. 可以是表达式,有返回值
  3. 支持范围、类型、任意条件判断
  4. 类型检查后自动 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 一个本质区别:几乎所有东西都是表达式(有返回值)。

控制流JavaKotlin
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 b

3.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 触发条件

  1. 变量是 val(不可变,编译器才敢做这个推断)
  2. 中间没有可能改变 obj 的代码
  3. 不是开放类的属性(多线程可能改)

3.6.2 as 强制转换

kotlin
val s: String = obj as String              // 转不成抛 ClassCastException
val s: String? = obj as? String            // 安全转换:转不成返回 null

3.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..101 until 10 写循环
  • ✅ 知道 if / when / try 都是表达式

🎤 3.9 章末面试题(10 道)

Q1. valvar 的区别是什么?编译后变成什么?

  • 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. valconst val 有什么区别?

维度valconst 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 表达式

必须用 ${} 的场景

  1. 访问对象成员:${user.name}
  2. 调用方法:${list.size}
  3. 表达式计算:${a + b}
  4. 想紧跟字母数字下划线(避免歧义):"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 switchKotlin 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 操作符可以用在哪些地方?

  1. 范围检查x in 1..10
  2. 集合包含name in listOf("Alice", "Bob")
  3. 字符串包含'a' in "abc"
  4. map 键检查"key" in map
  5. for 循环for (i in 1..10)
  6. 取反!in

底层是看对象有没有定义 contains 操作符。


Q9. 什么是 Smart Cast?什么时候 Smart Cast 不工作?

:在 is 检查后,编译器自动把变量当对应类型用,省去手动转型:

kotlin
fun foo(obj: Any) {
    if (obj is String) {
        println(obj.length)  // 自动当 String 用
    }
}

不工作的场景

  1. var 变量(值可能在中间被改)—— 改用 val
  2. 开放类的属性(多线程可能改)—— 用本地 val 复制一份
  3. 可空类型 ?.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)}")
}

BasicSyntax.kt ↗ · GradeChecker.kt ↗