Kotlin 练习题(15 题)
Kotlin 学了语法不动手写等于白学。这一页给 15 道精选练习题,按难度分三级——基础 6 题(变量 / 运算 / 控制流 / 函数)、进阶 5 题(重点攻克空安全、when、data class)、实战 4 题(综合应用)。每题都含题目描述、提示、可折叠的参考答案。
使用建议:先别急着展开答案。读题后自己打开 IntelliJ IDEA、Android Studio 或 Kotlin Playground 试着写,卡住了再看提示,实在写不出再对照参考答案。Kotlin 答案不止一种,只要能编译通过、可读性强就是好代码。
基础(6 题):变量 / 运算 / 控制流 / 函数
这一组覆盖 Kotlin 最基础的语言元素,目标是用最短的代码解决一个小问题,训练"把思路翻译成代码"的能力,同时熟悉 for-in、when、fun 等高频语法。
第 1 题:翻转字符串
题目:写一个函数 reverseString(s: String): String,把字符串翻转过来。比如 "hello" 翻转成 "olleh"。
提示:Kotlin 的 String.reversed() 直接返回倒序字符串;= 单表达式函数体可以省略大括号。也可以转 toCharArray() 双指针翻转。
参考答案
// 方法 1:reversed() 直接对 String 用,返回倒序字符串
fun reverseString(s: String): String = s.reversed()
// 方法 2:转字符数组手动双指针翻转(理解原理)
fun reverseString2(s: String): String {
val arr = s.toCharArray()
var i = 0; var j = arr.size - 1
while (i < j) {
val tmp = arr[i]; arr[i] = arr[j]; arr[j] = tmp
i++; j--
}
return String(arr)
}
fun main() {
println(reverseString("hello")) // olleh
println(reverseString("Kotlin")) // niltoK
println(reverseString2("你好世界")) // 界世好你
}
// 输出: olleh第 2 题:判断素数
题目:写一个函数 isPrime(n: Int): Boolean,判断正整数 n 是否为素数。
提示:小于 2 的都不是素数;只需检查从 2 到 sqrt(n)——超过平方根的因子必然配对一个已经检查过的更小因子。Kotlin 用 Math.sqrt(n.toDouble()).toInt()。
参考答案
fun isPrime(n: Int): Boolean {
if (n < 2) return false
// 只需检查到 sqrt(n):超过的因子必然配对一个更小的、已检查过的因子
val limit = Math.sqrt(n.toDouble()).toInt()
for (i in 2..limit) {
if (n % i == 0) return false
}
return true
}
fun main() {
println(isPrime(2)) // true
println(isPrime(7)) // true
println(isPrime(10)) // false
println(isPrime(1)) // false
}
// 输出: true第 3 题:FizzBuzz
题目:打印 1 到 15。3 的倍数打印 Fizz,5 的倍数打印 Buzz,同时是 3 和 5 的倍数打印 FizzBuzz,其余打印数字本身。
提示:Kotlin 用 when { } 表达式替代 if-else 链,更紧凑;判断顺序很关键——必须先判断"同时是 3 和 5 的倍数"(即 15 的倍数)。区间用 1..15。
参考答案
// Kotlin 推荐用 when 表达式替代 if-else 链,更紧凑
fun main() {
for (i in 1..15) {
val out = when {
i % 15 == 0 -> "FizzBuzz" // 必须先判断 15(否则会被 3 或 5 截胡)
i % 3 == 0 -> "Fizz"
i % 5 == 0 -> "Buzz"
else -> i.toString()
}
println(out)
}
}
// 输出(节选):1, 2, Fizz, 4, Buzz, Fizz, 7, ..., 14, FizzBuzz第 4 题:数组最大值
题目:不使用内置 .maxOrNull(),写一个函数 findMax(arr: List<Int>): Int?,返回列表中的最大值;空列表返回 null。
提示:arr.drop(1) 返回去掉首个元素后的列表;遍历比较、遇到更大的就更新。Kotlin 的 Int? 自动表达"可能为 null"。
参考答案
// 方法 1:手写遍历(空集合返回 null,所以返回 Int?)
fun findMax(arr: List<Int>): Int? {
if (arr.isEmpty()) return null
var m = arr[0]
for (x in arr.drop(1)) if (x > m) m = x
return m
}
fun main() {
// 方法 2:实际项目直接用 .maxOrNull()
println(findMax(listOf(3, 1, 4, 1, 5, 9, 2, 6))) // 9
println(listOf(3, 1, 4, 1, 5, 9, 2, 6).maxOrNull()) // 9
println(findMax(emptyList())) // null
}
// 输出: 9第 5 题:数组求和
题目:给定一个整数列表,返回所有元素的和。至少写两种写法。
提示:List<Int>.sum() 是最直接的;reduce { a, b -> a + b } 通用但无初始值;fold(0) { a, b -> a + b } 给初始值更安全。
参考答案
fun main() {
val nums = listOf(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
val total1 = nums.sum() // List<Int>.sum() 最常用
val total2 = nums.reduce { a, b -> a + b } // 通用 reduce(无初始值)
val total3 = nums.fold(0) { a, b -> a + b } // fold 给初始值(更安全)
println(total1) // 55
println(total2) // 55
println(total3) // 55
}
// 输出: 55第 6 题:计算阶乘
题目:写一个函数 factorial(n: Int): Long,返回 n!。要求 factorial(0) == 1,且对负数调用抛 IllegalArgumentException。
提示:require(n >= 0) { "..." } 是参数校验惯用法;(1..n).fold(1L) { acc, i -> acc * i } 累乘;递归版加 tailrec 修饰符可避免栈溢出。
参考答案
// 方法 1:fold 累乘,Long 防溢出
fun factorial(n: Int): Long {
require(n >= 0) { "阶乘不接受负数" } // require 是参数校验惯用法
return if (n == 0) 1L else (1..n).fold(1L) { acc, i -> acc * i }
}
// 方法 2:tailrec 尾递归,Kotlin 编译器会优化成循环,避免栈溢出
tailrec fun factorialRec(n: Int, acc: Long = 1): Long {
require(n >= 0) { "阶乘不接受负数" }
return if (n == 0) acc else factorialRec(n - 1, acc * n)
}
fun main() {
println(factorial(5)) // 120
println(factorial(0)) // 1
println(factorialRec(6)) // 720
}
// 输出: 120进阶(5 题):空安全 / when / data class
这一组聚焦 Kotlin 最具特色的部分——空安全(把"可能为 null"提到类型系统)、when(更强大的 switch)、data class(自动生成样板方法)。这是写出地道 Kotlin 代码的关键。
第 7 题:可空类型 ? 与安全调用 ?.
题目:声明一个 String? 和一个 Int? 类型的常量(一个有值、一个为 null),用 ?. 安全访问它们的属性。
提示:name?.length——name 为 null 时整条表达式返回 null,不抛 NPE;!! 强制解包,为 null 时抛 NPE,只在确定不会为 null 时用。
参考答案
fun main() {
// 用 ? 声明可空类型;安全调用 ?. 遇到 null 直接返回 null,不抛 NPE
val name: String? = "Alice"
val age: Int? = null
println(name?.length) // 5(Alice 不为 null,正常调用 length)
println(age?.toString()) // null(age 为 null,整条表达式返回 null)
// 直接 name.length 编译报错,编译器强制你处理 null
// !! 强制解包:为 null 时抛 NullPointerException,慎用
println(name!!.length) // 5
}
// 输出: 5第 8 题:Elvis 运算符 ?:
题目:给定 nickname: String? = null 和 realname: String? = "Alice",用 ?: 链式取第一个非 null 值,都为 null 时取 "匿名"。
提示:a ?: b 等价于"a 为 null 就取 b";链式 a ?: b ?: c 从左到右找第一个非 null。
参考答案
fun main() {
// ?: 当左侧为 null 时返回右侧默认值,类似 Swift ?? / Python or
val nickname: String? = null
val realname: String? = "Alice"
val display1 = nickname ?: "匿名"
// 链式:从左到右找第一个非 null
val display2 = nickname ?: realname ?: "匿名"
println(display1) // 匿名
println(display2) // Alice
}
// 输出: 匿名第 9 题:let 处理可空
题目:用 ?.let { } 安全执行一段需要非 null 值的代码块;为 null 时跳过。
提示:name?.let { println(it) }——name 非 null 时进入 lambda,it 是解包后的 String;比 if (name != null) 更函数式。
参考答案
fun main() {
val name: String? = "Bob"
// ?.let {}:非 null 时进入 lambda,it 就是解包后的值(替代 if-null 嵌套)
name?.let {
println("Hello, " + it + "!") // it 是 String,直接用
}
val age: Int? = null
age?.let { println("Age = " + it) } // 不执行
// 配合 Elvis 处理 null 分支
val n = age ?: 0
println("Age = " + n)
}
// 输出: Hello, Bob!第 10 题:when 表达式(多分支)
题目:写一个 describe(x: Any): String,根据输入类型返回不同描述:0 返回 "zero",1/2/3 返回 "small",4-100 返回 "medium",字符串返回长度,其余返回 "unknown"。
提示:when 支持 0 ->(单值)、1, 2, 3 ->(多值)、in 4..100 ->(范围)、is String ->(类型判断 + 自动 smart-cast)。
参考答案
// when 是更强大的 switch:支持多值、范围、类型判断,且作为表达式时必须穷尽
fun describe(x: Any): String = when (x) {
0 -> "zero"
1, 2, 3 -> "small" // 多值合并
in 4..100 -> "medium" // 范围判断
is String -> "string, length=" + x.length // 类型判断 + 自动转型
else -> "unknown"
}
fun main() {
println(describe(0)) // zero
println(describe(3)) // small
println(describe(50)) // medium
println(describe("hi")) // string, length=2
}
// 输出: zero第 11 题:数据类 data class
题目:定义 data class Point(x: Int, y: Int),演示基于值的相等比较、copy、解构声明。
提示:data class 自动生成 equals(基于字段值比较)、toString(形如 Point(x=1, y=2))、copy(字段=新值)(不可变更新)、componentN(解构)。
参考答案
// data class 自动生成 equals / hashCode / toString / copy / componentN
data class Point(val x: Int, val y: Int)
fun main() {
val p1 = Point(1, 2)
val p2 = Point(1, 2)
println(p1 == p2) // true(基于字段值比较,不是引用相等)
println(p1) // Point(x=1, y=2)
// copy 修改部分字段,常用于不可变更新
val p3 = p1.copy(y = 100)
println(p3) // Point(x=1, y=100)
// 解构声明:componentN 自动生成
val (x, y) = p1
println("x=" + x + ", y=" + y) // x=1, y=2
}
// 输出: true实战(4 题):综合应用
这一组每题综合用到多个知识点——data class、集合操作、不可变更新、when 分派——做出接近真实场景的小程序。建议每题完整跑一遍,再想想怎么扩展。
第 12 题:学生管理(data class + 排序)
题目:定义 data class Student(姓名 + 多科成绩 + 计算属性 average),把三个学生按均分降序输出。
提示:在类体内定义 val average: Double get() = ... 是计算属性;sortedByDescending { it.average } 降序;forEachIndexed 拿到下标。
参考答案
data class Student(val name: String, val scores: List<Double>) {
// 自定义 getter 计算均分;空列表返回 0.0 避免除零
val average: Double
get() = if (scores.isEmpty()) 0.0 else scores.sum() / scores.size
}
fun main() {
val students = listOf(
Student("小明", listOf(90.0, 80.0)),
Student("小红", listOf(95.0, 88.0)),
Student("小刚", listOf(70.0, 100.0)),
)
// sortedByDescending 按均分降序;it 是 lambda 隐式参数
val ranked = students.sortedByDescending { it.average }
ranked.forEachIndexed { i, s ->
println("" + (i + 1) + ". " + s.name + " 均分 " + s.average)
}
}
// 输出:
// 1. 小红 均分 91.5
// 2. 小明 均分 85.0
// 3. 小刚 均分 85.0第 13 题:词频统计(groupBy)
题目:统计一段英文文本中每个单词的出现次数,按频次降序输出 Top 3。
提示:split(" ").groupBy { it } 返回 Map<String, List<String>>,再 mapValues { it.value.size } 把每个 List 折叠成 size。
参考答案
fun main() {
val text = "the quick brown fox the lazy dog the fox"
// groupBy 按单词分组(返回 Map<String, List<String>>),再 mapValues 算 size
val freq = text.split(" ")
.groupBy { it }
.mapValues { it.value.size }
// 按频次降序取 Top 3
val top3 = freq.entries
.sortedByDescending { it.value }
.take(3)
top3.forEach { println(it.key + ": " + it.value) }
}
// 输出:
// the: 3
// fox: 2
// quick: 1第 14 题:集合操作(filter / map / sortedBy)
题目:给定一组商品,筛选出"手机",全部打 9 折,按价格升序,最后输出展示文案。
提示:链式 filter { }.map { }.sortedBy { }.map { };不可变更新用 it.copy(price = ...) 而不是直接修改字段。
参考答案
data class Product(val name: String, val price: Double, val category: String)
fun main() {
val products = listOf(
Product("iPhone", 9999.0, "phone"),
Product("Pixel", 5999.0, "phone"),
Product("iPad", 3999.0, "tablet"),
Product("Monitor", 1599.0, "computer"),
)
// 链式:筛选手机 → 全部 9 折 → 按价格升序 → 提取展示文案
val result = products
.filter { it.category == "phone" } // 留下手机
.map { it.copy(price = it.price * 0.9) } // 用 copy 改价格(不可变)
.sortedBy { it.price } // 升序
.map { it.name + ": ¥" + it.price }
result.forEach { println(it) }
}
// 输出:
// Pixel: ¥5399.1
// iPhone: ¥8999.1第 15 题:简易计算器(when)
题目:实现一个计算器,支持 + - * / 四则运算。要求:除数为 0 返回 null;非法运算符返回 null。
提示:用 when (op) 表达式分派;运算符用字符串即可;when 作为表达式直接返回,所有分支结果合并成 Double? 类型。
参考答案
// 用 when 表达式分派运算符,非法运算符或除零返回 null
fun calc(a: Double, op: String, b: Double): Double? {
return when (op) {
"+" -> a + b
"-" -> a - b
"*" -> a * b
"/" -> if (b == 0.0) null else a / b
else -> null // 非法运算符
}
}
fun main() {
println(calc(3.0, "+", 5.0)) // 8.0
println(calc(10.0, "/", 4.0)) // 2.5
println(calc(2.0, "*", 9.0)) // 18.0
println(calc(1.0, "/", 0.0)) // null
println(calc(5.0, "%", 3.0)) // null
}
// 输出: 8.0练习建议
不要只看答案。把每道题在 IntelliJ IDEA 或 Android Studio 亲手敲一遍,改一改输入试试边界情况(空列表、负数、超长字符串、null)。Kotlin 的类型系统比 Java 严格,编译器就是最好的老师。
试着扩展:做完第 12 题学生管理,能不能加上"按单科排名"?做完第 13 题词频统计,能不能从文件读?做完第 15 题计算器,能不能把运算符改成 sealed class 让非法运算符编译期就报错?每个扩展都是一次小型的真实开发。
更多练习资源:刷算法去 LeetCode / Codewars;读官方文档 kotlinlang.org(中文版同步);参与开源 Kotlin 项目去 GitHub。
← 上一篇 Kotlin 实战项目
返回 Kotlin 教程目录