Kotlin学习第 9 课:Kotlin 实战应用:从案例到项目

本节课将以 "实战驱动" 为核心,通过 3 个由浅入深的案例 / 项目,帮你把 Kotlin 基础语法、面向对象、集合、高阶函数、IO 流等知识点串联起来,真正做到 "学完能用"。每个案例都会先明确需求,再分步实现,最后总结核心知识点,确保你能跟上节奏并理解背后的逻辑。

一、实战案例一:简易计算器(基础语法综合应用)

计算器是入门级实战的经典案例,能帮你巩固函数定义、条件判断、输入输出、异常处理等基础语法,同时培养 "边界场景思考" 能力(比如输入不合法、除数为零)。

1. 需求分析

明确计算器的核心功能和边界:

  • 支持 加减乘除 4 种运算(运算符仅支持 + - * /);
  • 处理输入合法性:
    • 确保用户输入的 "数字" 是有效数字(比如拒绝 "abc""12a" 这类无效值);
    • 除法运算时,确保除数不为 0;
  • 支持 "多次计算",用户可选择继续计算或退出。

2. 分步实现

步骤 1:定义计算核心函数

先封装一个 "计算逻辑" 函数,接收两个数字和运算符,返回计算结果或错误信息(用密封类区分 "成功" 和 "失败",比直接返回 null 更清晰):

Kotlin 复制代码
// 密封类:区分计算结果的"成功"与"失败"
sealed class CalculationResult {
    data class Success(val result: Double) : CalculationResult() // 成功:携带结果
    data class Error(val message: String) : CalculationResult()   // 失败:携带错误信息
}

// 计算函数:接收两个数字、运算符,返回计算结果
fun calculate(num1: Double, num2: Double, operator: Char): CalculationResult {
    return when (operator) {
        '+' -> CalculationResult.Success(num1 + num2)
        '-' -> CalculationResult.Success(num1 - num2)
        '*' -> CalculationResult.Success(num1 * num2)
        '/' -> {
            if (num2 == 0.0) {
                CalculationResult.Error("错误:除数不能为0!")
            } else {
                CalculationResult.Success(num1 / num2)
            }
        }
        else -> CalculationResult.Error("错误:不支持的运算符(仅支持 + - * /)")
    }
}

步骤 2:处理用户输入(封装输入验证逻辑)

用户输入可能不合法(比如输 "abc" 当数字),所以封装两个工具函数:readValidNumber(读取有效数字)和 readValidOperator(读取有效运算符):

Kotlin 复制代码
// 读取有效数字:直到用户输入合法数字才返回
fun readValidNumber(prompt: String): Double {
    while (true) {
        print(prompt) // 提示用户输入(比如"请输入第一个数字:")
        val input = readLine() // 读取控制台输入
        // 尝试将输入转为Double,失败则提示重新输入
        val number = input?.toDoubleOrNull()
        if (number != null) {
            return number
        } else {
            println("输入无效!请输入一个合法的数字(比如 123 或 45.6)")
        }
    }
}

// 读取有效运算符:直到用户输入 +-*/ 才返回
fun readValidOperator(): Char {
    while (true) {
        print("请输入运算符(+ - * /):")
        val input = readLine()
        // 校验:输入不为空、长度为1、且是合法运算符
        if (input != null && input.length == 1 && input[0] in listOf('+', '-', '*', '/')) {
            return input[0]
        } else {
            println("输入无效!请输入 + - * / 中的一个运算符")
        }
    }
}

步骤 3:实现主逻辑(串联输入、计算、输出)

写一个主函数,实现 "循环计算" 的逻辑:用户计算一次后,可选择继续或退出:

Kotlin 复制代码
fun main() {
    println("=== 简易计算器 ===")
    while (true) {
        // 1. 读取用户输入(通过工具函数确保输入合法)
        val num1 = readValidNumber("请输入第一个数字:")
        val num2 = readValidNumber("请输入第二个数字:")
        val operator = readValidOperator()

        // 2. 调用计算函数,处理结果
        val result = calculate(num1, num2, operator)
        when (result) {
            is CalculationResult.Success -> println("计算结果:$num1 $operator $num2 = ${result.result}")
            is CalculationResult.Error -> println(result.message) // 打印错误信息
        }

        // 3. 询问用户是否继续
        print("是否继续计算?(y/n):")
        val continueInput = readLine()?.lowercase()
        if (continueInput != "y") {
            println("计算器已退出,再见!")
            break // 退出循环
        }
        println("-------------------") // 分割线,优化体验
    }
}

二、实战案例二:学生信息管理系统(面向对象与集合综合应用)

这个案例会用到 Kotlin 的面向对象(OOP)集合(Collection) ,模拟 "学生信息的增删改查",贴近实际开发中的 "数据管理" 场景。

1. 需求分析

核心功能是对 "学生信息" 的 4 个操作:

  • 添加学生:输入学生的 ID、姓名、年龄、成绩,确保 ID 唯一(不重复添加);
  • 查询学生:支持按 "学生 ID" 查询,返回该学生的所有信息;
  • 修改学生:按 ID 找到学生后,修改其姓名、年龄、成绩(ID 不可改);
  • 删除学生:按 ID 删除学生,删除前确认是否存在该学生;
  • 所有学生信息保存在内存中(用集合存储),支持控制台交互。

2. 分步实现

步骤 1:定义 Student 数据类(封装学生属性)

Kotlin 的data class会自动生成equals()hashCode()toString()等方法,非常适合存储 "数据模型"(比如学生、用户):

Kotlin 复制代码
// 学生数据类:id(唯一标识)、name、age、score
data class Student(
    val id: String,         // ID不可改(用val)
    var name: String,       // 姓名可改(用var)
    var age: Int,           // 年龄可改
    var score: Double       // 成绩可改
) {
    // 重写toString(),自定义打印格式(更易读)
    override fun toString(): String {
        return "学生信息:ID=$id,姓名=$name,年龄=$age,成绩=$score"
    }
}

步骤 2:实现 StudentManager 管理类(封装增删改查逻辑)

用 "管理类" 封装对学生集合的操作,避免业务逻辑散落在主函数中,符合 "单一职责原则":

Kotlin 复制代码
import java.util.* // 导入集合相关类

class StudentManager {
    // 用MutableList存储学生(支持增删改),初始为空
    private val studentList: MutableList<Student> = mutableListOf()

    // 1. 添加学生:确保ID唯一
    fun addStudent(student: Student): String {
        // 判断ID是否已存在(用any()检查集合中是否有相同ID的学生)
        val isIdExists = studentList.any { it.id == student.id }
        return if (isIdExists) {
            "添加失败:学生ID=${student.id}已存在!"
        } else {
            studentList.add(student)
            "添加成功!当前学生总数:${studentList.size}"
        }
    }

    // 2. 查询学生:按ID查询(返回Student?,null表示未找到)
    fun queryStudentById(id: String): Student? {
        // 用find()找到第一个匹配ID的学生
        return studentList.find { it.id == id }
    }

    // 3. 修改学生:按ID修改姓名、年龄、成绩
    fun updateStudent(id: String, newName: String, newAge: Int, newScore: Double): String {
        val studentToUpdate = queryStudentById(id)
        return if (studentToUpdate == null) {
            "修改失败:未找到ID=$id的学生!"
        } else {
            // 修改属性(因为name/age/score是var)
            studentToUpdate.name = newName
            studentToUpdate.age = newAge
            studentToUpdate.score = newScore
            "修改成功!${studentToUpdate}"
        }
    }

    // 4. 删除学生:按ID删除
    fun deleteStudent(id: String): String {
        val studentToDelete = queryStudentById(id)
        return if (studentToDelete == null) {
            "删除失败:未找到ID=$id的学生!"
        } else {
            studentList.remove(studentToDelete)
            "删除成功!当前学生总数:${studentList.size}"
        }
    }

    // 辅助功能:查看所有学生
    fun showAllStudents(): String {
        return if (studentList.isEmpty()) {
            "当前无学生信息!"
        } else {
            "=== 所有学生信息 ===\n" + studentList.joinToString(separator = "\n")
        }
    }
}

步骤 3:实现控制台交互逻辑(主函数)

通过 "菜单" 让用户选择操作,串联管理类的方法:

Kotlin 复制代码
fun main() {
    val studentManager = StudentManager()
    println("=== 学生信息管理系统 ===")

    while (true) {
        // 打印菜单
        println("\n请选择操作:")
        println("1. 添加学生")
        println("2. 查询学生(按ID)")
        println("3. 修改学生信息")
        println("4. 删除学生(按ID)")
        println("5. 查看所有学生")
        println("6. 退出系统")
        print("请输入操作编号(1-6):")

        // 读取用户选择的操作编号
        val choice = readLine()?.toIntOrNull() ?: 0 // 输入无效时默认0

        // 根据选择执行对应操作
        when (choice) {
            1 -> addStudentMenu(studentManager)    // 调用添加学生的子逻辑
            2 -> queryStudentMenu(studentManager)  // 调用查询学生的子逻辑
            3 -> updateStudentMenu(studentManager)  // 调用修改学生的子逻辑
            4 -> deleteStudentMenu(studentManager)  // 调用删除学生的子逻辑
            5 -> println(studentManager.showAllStudents())
            6 -> {
                println("系统已退出,再见!")
                break
            }
            else -> println("操作编号无效!请输入1-6之间的数字")
        }
    }
}

// 子逻辑:添加学生(拆分主函数代码,更清晰)
fun addStudentMenu(manager: StudentManager) {
    print("请输入学生ID:")
    val id = readLine() ?: ""
    print("请输入学生姓名:")
    val name = readLine() ?: ""
    print("请输入学生年龄:")
    val age = readLine()?.toIntOrNull() ?: 0
    print("请输入学生成绩:")
    val score = readLine()?.toDoubleOrNull() ?: 0.0

    // 校验必要信息(ID和姓名不能为空)
    if (id.isBlank() || name.isBlank()) {
        println("添加失败:ID和姓名不能为空!")
        return
    }
    if (age <= 0 || age > 150) {
        println("添加失败:年龄必须在1-150之间!")
        return
    }
    if (score < 0 || score > 100) {
        println("添加失败:成绩必须在0-100之间!")
        return
    }

    // 调用管理类的添加方法
    val result = manager.addStudent(Student(id, name, age, score))
    println(result)
}

// 子逻辑:查询学生(按ID)
fun queryStudentMenu(manager: StudentManager) {
    print("请输入要查询的学生ID:")
    val id = readLine() ?: ""
    val student = manager.queryStudentById(id)
    if (student != null) {
        println(student)
    } else {
        println("未找到ID=$id的学生!")
    }
}

// 子逻辑:修改学生信息
fun updateStudentMenu(manager: StudentManager) {
    print("请输入要修改的学生ID:")
    val id = readLine() ?: ""
    // 先查询学生是否存在
    val student = manager.queryStudentById(id)
    if (student == null) {
        println("未找到ID=$id的学生!")
        return
    }
    // 输入新信息(可保留原信息,比如按回车不修改)
    print("请输入新姓名(原姓名:${student.name}):")
    val newName = readLine()?.takeIf { it.isNotBlank() } ?: student.name
    print("请输入新年龄(原年龄:${student.age}):")
    val newAge = readLine()?.toIntOrNull() ?: student.age
    print("请输入新成绩(原成绩:${student.score}):")
    val newScore = readLine()?.toDoubleOrNull() ?: student.score

    // 校验新信息
    if (newAge <= 0 || newAge > 150) {
        println("修改失败:年龄必须在1-150之间!")
        return
    }
    if (newScore < 0 || newScore > 100) {
        println("修改失败:成绩必须在0-100之间!")
        return
    }

    // 调用管理类的修改方法
    val result = manager.updateStudent(id, newName, newAge, newScore)
    println(result)
}

// 子逻辑:删除学生(按ID)
fun deleteStudentMenu(manager: StudentManager) {
    print("请输入要删除的学生ID:")
    val id = readLine() ?: ""
    val student = manager.queryStudentById(id)
    if (student == null) {
        println("未找到ID=$id的学生!")
        return
    }
    // 二次确认(避免误删)
    print("确定要删除学生 ${student.name}(ID=$id)吗?(y/n):")
    val confirm = readLine()?.lowercase()
    if (confirm == "y") {
        val result = manager.deleteStudent(id)
        println(result)
    } else {
        println("已取消删除操作!")
    }
}

三、实战项目:文本处理工具(高阶函数与 IO 流应用)

这个项目难度稍高,会用到 Kotlin 的高阶函数IO 流(文件读写),模拟实际开发中的 "文本处理" 场景(比如统计文章字数、查找关键词)。

1. 需求分析

核心功能是对 "文本文件" 的处理:

  • 读取文本文件:支持从指定路径读取.txt 文件(处理 "文件不存在""权限不足" 等异常);
  • 文本统计:统计文件的 "总行数" 和 "总字数"(字数按空格 / 换行分割,过滤空字符);
  • 关键词查找:查找文件中包含指定关键词的所有行,返回行号和内容;
  • 生成处理报告 :将统计结果和关键词查找结果,写入新的报告文件(如report.txt);
  • 支持用户输入文件路径、关键词,交互友好。

2. 分步实现(附完整代码)

步骤 1:导入 IO 相关依赖

Kotlin 的 IO 操作依赖kotlin.io包(默认包含,无需额外导入),但文件操作需要java.io.File类,所以先导入:

kotlin 复制代码
import java.io.File
import java.io.FileNotFoundException
import kotlin.io.path.exists

步骤 2:实现文件读取与写入(处理异常)

文件操作可能出现异常(如文件不存在、权限不够),用try-catch捕获,避免程序崩溃:

Kotlin 复制代码
/**
 * 读取文件内容
 * @param filePath 文件路径(如 "D:/test.txt")
 * @return 成功返回文件内容(String),失败返回错误信息(String)
 */
fun readFileContent(filePath: String): String {
    return try {
        // 用Kotlin扩展函数readText()读取文件(自动处理流关闭)
        File(filePath).readText(Charsets.UTF_8)
    } catch (e: FileNotFoundException) {
        "读取失败:文件不存在(路径:$filePath)"
    } catch (e: SecurityException) {
        "读取失败:权限不足,无法访问文件"
    } catch (e: Exception) {
        "读取失败:未知错误(${e.message})"
    }
}

/**
 * 写入报告到文件
 * @param reportContent 报告内容
 * @param outputPath 输出路径(默认 "report.txt",在项目根目录)
 * @return 成功返回true,失败返回false
 */
fun writeReport(reportContent: String, outputPath: String = "report.txt"): Boolean {
    return try {
        // 用Kotlin扩展函数writeText()写入文件
        File(outputPath).writeText(reportContent, Charsets.UTF_8)
        println("报告已生成:$outputPath")
        true
    } catch (e: SecurityException) {
        println("写入失败:权限不足,无法创建文件")
        false
    } catch (e: Exception) {
        println("写入失败:未知错误(${e.message})")
        false
    }
}

步骤 3:实现文本统计函数(用高阶函数简化逻辑)

统计 "行数" 和 "字数",用高阶函数let/run简化判空和流程:

Kotlin 复制代码
/**
 * 统计文本的行数和字数
 * @param content 文本内容(从文件读取的字符串)
 * @return Pair(总行数, 总字数)
 */
fun countTextInfo(content: String): Pair<Int, Int> {
    // 用run函数串联操作:先分割行,再统计
    return content.run {
        // 统计行数:按换行符分割,过滤空行(如连续换行)
        val lines = split("\n", "\r\n") // 兼容Windows(\r\n)和Linux(\n)换行
        val lineCount = lines.filter { it.isNotBlank() }.size

        // 统计字数:按空格/制表符分割,过滤空字符
        val wordCount = lines.flatMap { line ->
            line.split("\s+".toRegex()) // 正则:匹配1个或多个空白字符
        }.filter { it.isNotBlank() }.size

        Pair(lineCount, wordCount) // 返回行数和字数的配对
    }
}

步骤 4:实现关键词查找函数(返回行号和内容)

查找包含关键词的行,返回 "行号 + 行内容" 的列表,支持忽略大小写:

Kotlin 复制代码
/**
 * 查找包含关键词的行
 * @param content 文本内容
 * @param keyword 关键词
 * @param caseSensitive 是否区分大小写(默认false,不区分)
 * @return 匹配的行列表(格式:"行号:内容")
 */
fun findKeywordLines(content: String, keyword: String, caseSensitive: Boolean = false): List<String> {
    if (keyword.isBlank()) {
        return listOf("关键词不能为空!")
    }

    // 分割行,遍历每一行(记录行号)
    return content.split("\n", "\r\n")
        .withIndex() // 给每一行添加索引(行号,从1开始)
        .filter { (index, line) ->
            // 判断行是否包含关键词(处理大小写)
            val lineToCheck = if (caseSensitive) line else line.lowercase()
            val keywordToCheck = if (caseSensitive) keyword else keyword.lowercase()
            lineToCheck.contains(keywordToCheck)
        }
        .map { (index, line) ->
            // 格式化结果:行号(索引+1)+ 内容
            "第${index + 1}行:$line"
        }
}

步骤 5:实现主逻辑(串联所有功能)

让用户输入文件路径、关键词,生成完整报告:

Kotlin 复制代码
fun main() {
    println("=== 文本处理工具 ===")

    // 1. 读取用户输入的文件路径
    print("请输入要处理的文本文件路径(如 D:/test.txt):")
    val filePath = readLine() ?: ""
    if (filePath.isBlank()) {
        println("路径不能为空!程序退出。")
        return
    }

    // 2. 读取文件内容
    println("\n正在读取文件...")
    val fileContent = readFileContent(filePath)
    // 判断是否读取成功(成功的内容不会包含"失败"关键词,简单判断)
    if (fileContent.startsWith("读取失败")) {
        println(fileContent)
        return
    }
    println("文件读取成功(内容长度:${fileContent.length} 字符)")

    // 3. 统计文本信息
    println("\n正在统计文本信息...")
    val (lineCount, wordCount) = countTextInfo(fileContent)
    val statsReport = "=== 文本统计结果 ===\n" +
            "总行数(非空):$lineCount\n" +
            "总字数(非空):$wordCount\n"

    // 4. 关键词查找
    print("\n请输入要查找的关键词(留空则不查找):")
    val keyword = readLine() ?: ""
    val keywordReport = if (keyword.isNotBlank()) {
        println("正在查找关键词:$keyword...")
        val matchedLines = findKeywordLines(fileContent, keyword)
        "\n=== 关键词查找结果(关键词:$keyword)===\n" +
                if (matchedLines.isEmpty()) {
                    "未找到包含关键词的行\n"
                } else {
                    "匹配行数:${matchedLines.size}\n" + matchedLines.joinToString(separator = "\n") + "\n"
                }
    } else {
        "\n(未指定关键词,跳过查找)\n"
    }

    // 5. 生成完整报告
    val fullReport = statsReport + keywordReport +
            "=== 报告生成时间 ===" + "\n" +
            "处理文件路径:$filePath\n"

    // 6. 打印报告并写入文件
    println("\n=== 完整处理报告 ===")
    println(fullReport)
    writeReport(fullReport)

    println("\n处理完成!")
}
相关推荐
阿杆3 小时前
同事嫌参数校验太丑,我直接掏出了更优雅的 SpEL Validator
java·spring boot·后端
Grey Zeng12 小时前
Java SE 25新增特性
java·jdk·jdk新特性·jdk25
雨白13 小时前
Java 线程通信基础:interrupt、wait 和 notifyAll 详解
android·java
诺诺Okami17 小时前
Android Framework-Launcher-UI和组件
android
架构师沉默17 小时前
设计多租户 SaaS 系统,如何做到数据隔离 & 资源配额?
java·后端·架构
潘潘潘18 小时前
Android线程间通信机制Handler介绍
android
潘潘潘18 小时前
Android动态链接库So的加载
android
Java中文社群19 小时前
重要:Java25正式发布(长期支持版)!
java·后端·面试
潘潘潘19 小时前
Android多线程机制简介
android