CodeGym /课程 /Kotlin SELF /如何在 Kotlin 中结束函数:return

如何在 Kotlin 中结束函数:return

Kotlin SELF
第 8 级, 课程 2
可用

1. 为什么需要 return,不能“只写代码”吗?

如果你把所有的代码都写在 main 函数里,就像是在讲一个没有句号和段落的故事:虽然大概能听懂,但很快就会让人感到疲惫,而且很难找到“哪里出了问题”。 函数就像一个小故事,而 return 就是结束这个想法并将结果交付给外部世界的句号。 如果没有 return,一个负责计算的函数要么无法通过编译,要么其行为会与你的预期不符。

想象一个现实生活中的场景:你问朋友:“帮我算算 1000 USDT 能找回多少零钱?” 你的朋友可以:(1) 回答一个数字(“220”),(2) 只做一个动作(例如,把那个数字写在纸上给你),或者 (3) 陷入对金钱意义的无尽思考中。 在 Kotlin 中,这大致对应于 Int、Unit 和 Nothing。

2. return 与函数结果类型

当我们编写 fun sum(a: Int, b: Int): Int 时, 我们做出了一个承诺:“调用这个函数会得到一个 Int”。 编译器会严格检查这个承诺,否则程序可能会出现意想不到的“惊喜”。

因此,今天讲座的核心规则是:如果函数签名指定了结果类型(例如 : Int), 那么 在任何执行路径上,函数都必须返回该类型的值。

在实践中这意味着:你可以提前退出函数,可以使用 if/else, 可以使用循环,但编译器必须能够确认你最终 一定会 返回一个 Int。 否则,它会提示类似“我无法证明你返回了一个值”的错误——它是对的。

一个正确方法的简单示例:

fun sum(a: Int, b: Int): Int {
    return a + b
}

fun main() {
    val r = sum(10, 20)
    println(r) // 30
}

而这是一个 无法通过编译 的示例,因为其中一个分支没有返回任何内容:

fun absBad(x: Int): Int {
    if (x < 0) {
        return -x
    }
    // 这里编译器会问:如果 x >= 0,该返回什么?
}

在所有分支路径上都有明确返回值的正确版本:

fun absInt(x: Int): Int {
    if (x < 0) {
        return -x
    }
    return x
}

fun main() {
    println(absInt(-7)) // 7
    println(absInt(7))  // 7
}

为了加深理解,可以将执行过程想象成“箭头”。函数是一个盒子,return 就是盒子的出口:

flowchart TD
    A["调用 absInt(x)"] --> B{ x < 0 ? }
    B -->|是| C[return -x]
    B -->|否| D[return x]

3. 提前返回:减少嵌套,提升可读性

随着经验的增长,你会发现一个令人不悦的事实:if 套 if 再套 if 的层级越多, 你就越想合上电脑去种地。

提前返回(Early return,也称为卫语句 guard clause)是一种让代码趋于线性的风格: 首先过滤掉无效情况并退出,然后直接编写“正常场景”,无需额外的缩进。

重要提示:提前返回不是什么“旁门左道”;它是一个提高代码可读性的标准工具。 但也要注意不要过度使用,以免函数变成一堆“随处可见”的出口。

让我们想象一个计算折扣价格的函数。折扣必须在 0..100 之间,否则我们可能会亏钱。

fun discountedPrice(price: Int, discountPercent: Int): Int {
    if (price < 0) return 0
    if (discountPercent < 0) return price
    if (discountPercent > 100) return 0

    val discount = price * discountPercent / 100
    return price - discount
}

fun main() {
    println(discountedPrice(1000, 25)) // 750
}

注意这种风格:“异常情况——立即退出”,“正常情况——放在最后”。 这减少了嵌套,有助于大脑顺着代码逻辑走,而不会跟丢。

还有一点非常重要:return 会 立即结束函数。 这意味着在同一个分支中,return 下方的任何代码都不会被执行。 有时初学者会以为 return 的意思是“返回一个值但继续运行”,就像 println 一样。 不,它是真正的退出。

4. Unit:不带结果的纯操作函数

有时函数不返回任何内容,只是执行一项操作:打印文本、显示菜单、写入日志、增加尝试次数。 对于此类函数,Kotlin 使用结果类型 Unit。 它非常类似于其他语言中的 void,但从技术上讲,它是一个真正的类型,且只有一个可能的实例(即 Unit 本身)。

通常你不需要在签名中显式写出 Unit,因为 Kotlin 默认:如果你没有指定结果类型,它就是 Unit。 但在示例和文档中,你有时会看到带 Unit 的签名,例如: fun foo(): Unit —— 这样写是为了清晰或演示。

纯操作函数的示例:

fun printHeader(title: String) {
    println("==== $title ====")
}

fun main() {
    printHeader("折扣计算器")
    // ==== 折扣计算器 ====
}

这里没有什么可返回的;调用 printHeader(...) 只是执行场景中的一个步骤。

为了让你不产生困惑,这里有一个对比表:

函数行为 结果类型 能否赋值给 val x = ... 典型示例
计算值
Int, String, Boolean, ...
可以
val total = sum(1, 2)
执行操作
Unit
形式上可以,但几乎从不需要
printHeader("...")
从不返回
Nothing
不可以(因为不返回控制权) 无限循环

Unit 函数中的 return

如果一个函数返回 Unit,并不意味着禁止使用 return。 相反:在 Unit 函数中使用 return 是提前退出的绝佳方式。 它看起来只是有点不同:后面不跟任何值。也就是说,只写 return,不写数字或字符串。

当你希望在某些情况下“什么都不做”,但又不希望将所有有用的代码包装在一个巨大的 if 块中时,这非常方便。

示例:仅针对正数打印消息。

fun printIfPositive(x: Int) {
    if (x <= 0) return
    println("正数: $x")
}

fun main() {
    printIfPositive(-3) // (不打印任何内容)
    printIfPositive(10) // 正数: 10
}

另一个例子:打印菜单,但如果标题为空则退出且不打印,因为“没有名字的菜单”看起来很糟糕。

fun printMenu(title: String) {
    if (title.isBlank()) return

    println("== $title ==")
    println("1) 计算折扣")
    println("2) 退出")
}

fun main() {
    printMenu("菜单")
    // == 菜单 ==
    // 1) 计算折扣
    // 2) 退出
}

5. Nothing:不返回控制权的函数

Nothing 这个类型听起来像恐怖电影的名字:“无物:编译器归来”。 但在实践中,这是一个非常有趣的想法:结果类型为 Nothing 的函数 永远不会正常结束。 也就是说,它无法将控制权交还给调用它的位置。

对于目前你所学的知识来说,深入探讨这种典型情况还为时过早,因此 Nothing 最清晰的例子就是无限循环。

Kotlin 还将 Nothing 作为类型系统的一部分:例如,在泛型描述中,你可能会遇到类似 Function<in Nothing, out Any?> 的结构——意味着“无法安全地传入任何内容”,而这正是通过 Nothing 来表达的。 目前,只要记住这个概念即可:Nothing 意味着“到此为止,火车不再往前开了”。

最直观的例子:

fun forever(): Nothing {
    while (true) {
        // 我们在这里陷入死循环。
        // 在实际程序中,不应该这样做。
    }
}

既然“不应该这样做”,为什么它还有用呢? 因为 Kotlin 的类型系统追求严谨:如果编译器知道“此后执行将不再继续”, 它就能更好地分析代码和类型。

在你目前的阶段,通俗的理解更重要: Nothing 不是“空值”,而是一个“控制权不会返回”的承诺。

6. 迷你应用

为了不让话题只停留在抽象概念上,让我们构建一个简单的控制台应用程序来计算折扣价格。 我们将有意将逻辑拆分为多个函数,以便看到所有三种变体: 计算函数 (Int)、 纯操作函数 (Unit) 以及 Nothing 函数示例(作为概念演示,而非正常业务流程的一部分)。

计算函数:计算折扣并返回 Int

首先,我们编写一个纯粹的计算函数:它不打印任何内容,不读取输入,只负责计算并返回结果。 这样的函数更容易进行“脑内测试”和重用。

fun discountedPrice(price: Int, discountPercent: Int): Int {
    if (price < 0) return 0
    if (discountPercent < 0) return price
    if (discountPercent > 100) return 0

    val discount = price * discountPercent / 100
    return price - discount
}

fun main() {
    println(discountedPrice(1000, 25)) // 750
}

在这里我们已经使用了提前返回语句,这并非偶然: 过滤掉异常值比把它们带入整个逻辑要容易得多。

纯操作函数:打印结果

现在让我们添加一个仅负责打印的函数。它不向外部返回任何内容,其目的是执行操作。

fun printResult(original: Int, discount: Int, result: Int) {
    println("原价: $original")
    println("折扣: $discount%")
    println("最终价: $result")
}

fun main() {
    val result = discountedPrice(1000, 25)
    printResult(1000, 25, result)
    // 原价: 1000
    // 折扣: 25%
    // 最终价: 750
}

请注意:printResult 不返回值,但这并不奇怪。 在程序中,有一半的函数就是这样的:“去执行”,而不是“去计算”。

不带值的 return:如果输入异常,则温和地退出打印

如果数据没有意义,有时最好不要执行操作。 假设如果原价为零,我们就不想打印结果(仅用于演示——因为“给空气打折”听起来太哲学了)。

fun printResultIfMeaningful(original: Int, discount: Int, result: Int) {
    if (original == 0) return
    println("打折后的总价: $result")
}

fun main() {
    printResultIfMeaningful(0, 25, 0)      // (无输出)
    printResultIfMeaningful(1000, 25, 750) // 打折后的总价: 750
}

瞧:不带值的 return —— Unit 函数的一个常规工具。

组装 main:输入、计算、输出

现在我们有足够的组件来组装一个简单的场景了。 目前我们不使用智能输入,也不使用异常错误处理——那是之后的内容。 这里重要的是观察返回值如何在函数之间“流动”。

fun main() {
    println("请输入价格:")
    val price = readln().toInt()

    println("请输入折扣 (0..100):")
    val discount = readln().toInt()

    val result = discountedPrice(price, discount)
    printResult(price, discount, result)
}

注意其中的组合关系: discountedPrice(...) 返回一个 Int, 我们将其存入 result,然后将其传递给 printResult(...), 后者返回 Unit 并简单地打印结果。

演示 Nothing:“一个不会松手的函数”

最后,展示一小段 Nothing。 在正常的应用程序中不应该这样做,但为了理解这个类型,看一次是很有用的。

fun freeze(): Nothing {
    while (true) {
        println("我陷入了循环,回不去了……")
    }
}

fun main() {
    println("死循环之前")
    freeze()
    println("死循环之后") // 这一行永远不会执行
}

你永远不会在控制台中看到“死循环之后”。而这正是 Nothing 的含义:控制权不会返回。

7. 常见错误

错误 #1:在带有结果类型的函数中,并非所有分支都返回了值。
最常见的情况是 if 内部有 return,然后觉得“总会有办法的”。 编译器无法读心,也没有义务去猜测。 修复方法很简单:要么在 if 之后添加一个 return, 要么写一个完整的 if/else,确保两个分支都返回所需的类型。

错误 #2:尝试从 Unit 函数中“返回一个值”。
初学者有时会在声明为 fun f() { ... } 的函数内写 return 5。 编译器会报错,而且它是对的:要么函数负责计算并必须带有 : Int, 要么它负责执行操作并返回 Unit。

错误 #3:期望 return 像 break 一样工作,或者“只是结束 if”。
return 结束的是函数本身,而不是 if 块或循环。 如果你在循环内部写了 return,你将退出整个函数。 对于循环,我们有其他工具(break/continue)。

错误 #4:将 Nothing 当作“空结果”。
Nothing 不是“空值”,也不是“不返回任何内容”。 它字面上的意思是“完全不返回”。 如果你看到 Nothing,请联想到无限循环(在今天的讲座范围内), 并意识到调用该函数之后的代码将无法运行。

错误 #5:在没有明确理由的情况下在计算函数内部进行打印。
当一个函数既负责计算又负责打印时,它就很难重用:你本想只获取一个数字,结果它已经在控制台里打印了东西。 有时这没问题(尤其是在小脚本中),但通常最好职责分离: 一个函数返回一个值,另一个负责打印(例如通过 println)。 这使程序更可预测,也更容易调试。

1
任务
Kotlin SELF, 第 8 级, 课程 2
已锁定
振动传感器
振动传感器
1
任务
Kotlin SELF, 第 8 级, 课程 2
已锁定
方向指示器
方向指示器
1
任务
Kotlin SELF, 第 8 级, 课程 2
已锁定
安全除法
安全除法
1
任务
Kotlin SELF, 第 8 级, 课程 2
已锁定
活动海报
活动海报
评论
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION