Throw 和 Throws 是编程语言中异常抛出和声明的机制。throw 运算符中断函数的正常执行并将错误对象向上传递到调用栈。函数签名中的 throws 关键字警告调用方可能发生错误,使代码具有可预测性。根据 Kotlin Documentation (2026),Kotlin 中的 throw 是一个表达式(expression)而非语句,允许在 when 块和 elvis 运算符中使用它。
要点
Throw 是一个在程序执行点生成异常的运算符。当遇到 throw 时,当前执行流程立即中断,控制权转移到调用栈中最近的 catch 处理程序。如果找不到处理程序,应用程序将异常终止。在移动开发中,throw 用于标记在当前抽象层无法处理的错误——例如,服务器响应错误、网络不可用或参数无效。
Throws 是函数签名中的修饰符(主要在 Swift 中),声明该函数可能抛出错误。这是 Swift 中 checked errors 机制的一部分:调用方必须通过 do-catch、try?、try! 处理错误,或将自己的函数标记为 throws 以进一步传播(propagation)。在 Kotlin 和 Java 中 throws 也存在,但 Kotlin 认为它是多余的——Kotlin 中的所有异常都是 unchecked 的,即无需语法强制即可不处理。根据 Apple Swift Documentation (2026),Swift 中的 throws 是在函数类型中显式声明错误可能性的唯一方式,使 API 合约对开发人员透明。
throw 和 throws 之间的区别是根本性的:throw 是一个动作(运行时抛出异常),throws 是一个声明(编译时合约)。没有 throws 的函数不能使用 throw——Swift 编译器会报错。有 throws 的函数可以不用 throw——这是允许的,但没有意义。这种区分使 throw/throws 成为强大的 API 设计工具,错误合约在函数调用之前就可见于函数签名中。
在 Swift 中,throw 运算符接受任何实现 Error 协议的类型。最常见的是带有不同错误类型枚举值(case)的 enum。Swift 不支持 Java 风格的 checked exceptions——相反,函数类型标记为 throws,处理责任落在调用方身上。这使得 Swift 中的 throw 更加灵活,但对开发人员也提出了更高的要求。
任何符合 Error 协议的类型都可以通过 throw 抛出。开发人员通常使用不带关联值的枚举(用于简单错误)或带关联值的枚举(用于传递上下文)。Swift 不要求错误必须是枚举——可以使用实现 Error 的 struct 或 class,但由于处理程序端的 exhaustive switch,枚举更受青睐。编译器会检查 do-catch 中是否处理了所有枚举值。
enum AuthError: Error {
case invalidCredentials
case tokenExpired
case accountLocked(remainingMinutes: Int)
}
func login(username: String, password: String) throws -> Session {
guard isValid(username) else {
throw AuthError.invalidCredentials
}
let response = try api.authenticate(username, password)
if response.isLocked {
throw AuthError.accountLocked(
remainingMinutes: response.lockDuration
)
}
return Session(token: response.token)
}
AuthError 定义了三种场景:凭据错误、令牌过期和帐户被锁定(带有关联值 remainingMinutes)。函数 login 声明为 throws——编译器要求通过 try 调用。在函数内部,throw 在两个地方使用:用户名无效和帐户被锁定。关联值 accountLocked 允许向用户传递具体数据——等待解锁需要多少分钟。这种方法消除了为检查锁定状态而设置单独 API 端点的需要。
在 Kotlin 中,throw 是 Nothing 类型的表达式(expression),而不是语句。这意味着 throw 可以用于赋值语句的右侧、when 表达式内部以及 elvis 运算符 ?: 中。Nothing 类型是 Kotlin 中所有类型的特殊子类型,允许在需要任何类型值的地方使用 throw。编译器知道 throw 之后执行不会继续,因此不要求为此情况提供分支。
Nothing 是 Kotlin 中一个独特的类型,它是所有可能类型的子类型。返回 Nothing 的函数(例如 TODO())永远不会正常完成——要么总是抛出异常,要么进入无限循环。这使得 throw 成为需要值的位置的自然候选:elvis 运算符、不带 else 的 when、变量初始化。如果 throw 在 when 分支中,编译器知道该分支通向 Nothing,并且不要求为该分支提供 return 或 else。
data class Config(val apiUrl: String, val timeoutSec: Int)
class ConfigParser {
fun parse(json: String): Config {
val obj = JSONObject(json)
val url = obj.optString("apiUrl")
?: throw IllegalArgumentException("apiUrl is required")
val timeout = obj.optInt("timeoutSec", 30)
return Config(url, timeout)
}
fun getErrorMessage(code: Int): String {
return when (code) {
404 -> "Not found"
500 -> "Server error"
else -> throw IllegalArgumentException("Unknown code: $code")
}
}
}
在第一个示例中,throw 用于 elvis 运算符 ?::如果 JSON 中缺少 apiUrl 字段,则 throw 表达式立即中断执行并抛出 IllegalArgumentException。Nothing 类型允许编译器推断右侧类型为 String(elvis 期望 String,throw 类型为 Nothing,Nothing 是 String 的子类型)。在第二个示例中,throw 在 when 表达式内部:如果代码与任何已知代码都不匹配,则抛出异常。编译器知道 throw 之后的代码不可达,因此函数返回类型 String 不受影响。
在 Swift 中,throws 写在参数列表之后、返回类型箭头之前。带有 throws 的函数只能在 do-catch 内部或使用 try? 调用其他 throws 函数。如果 throws 函数没有处理错误,它会将错误传递给调用方。Swift 还支持 rethrows——用于接受 throws 闭包并将其错误传播的高阶函数的修饰符。rethrows 意味着函数只在传递的闭包抛出错误时才会抛出错误——函数本身不会生成错误。
func mapValues<T>(
_ array: [T],
transform: (T) throws -> U
) rethrows -> [U] {
var result = [U]()
for element in array {
result.append(try transform(element))
}
return result
}
// 与 throws 闭包一起使用
let parsed = try mapValues(jsonStrings) { str in
let data = Data(str.utf8)
return try JSONDecoder().decode(Item.self, from: data)
}
rethrows 使 mapValues 函数具有灵活性:它既可以接受 throws 闭包,也可以接受普通闭包。如果传递的是 throws 闭包,则调用 mapValues 需要 try;如果是普通闭包,则不需要 try。这使得 rethrows 非常适合 Swift 标准库中的高阶函数,如 map、filter、reduce。Apple 建议:对接受 throws 闭包且唯一错误源是该闭包的 API 使用 rethrows。如果函数可能抛出自己的错误,请使用 throws。
Swift 的 throws 更接近 checked exceptions(如 Java)——抛出的错误在签名中声明。Kotlin 和 Dart 使用 unchecked 异常——签名中不需要 throws。区别是根本性的:checked 强制开发人员处理(更安全但更冗长),unchecked 提供了自由,但增加了忘记处理错误的风险。Swift 为 throws 选择了 checked 方式,Kotlin 则为所有异常选择了 unchecked 方式。两种方法各有优势:Swift 在语言层面更安全,Kotlin 更紧凑,在函数式转换链中更方便。
为了在移动应用程序中进行结构化的错误处理,建议创建自定义错误类型,而不是使用基本的 Exception 或 Error。在 Swift 中使用符合 Error 协议的枚举,在 Kotlin 中使用继承自 Throwable(或 Exception)的密封类,在 Dart 中使用继承自 Exception 的类。自定义类型允许按类别对错误进行分组并传递关联数据。
| 语言 | 错误类型 | 特点 |
|---|---|---|
| Swift | enum: Error { ... } | 关联值,catch 中的 exhaustive switch |
| Kotlin | sealed class : Throwable() | 带字段的 data class,when 表达式 |
| Dart | class implements Exception | Message 字段,catch 中的 on 子句 |
| Java | class extends Exception | Checked vs unchecked,签名中必须使用 throws |
在设计自定义错误时,请遵循以下原则:一个错误对应一个场景。不要将不同的原因合并到带有 String message 标志的类型中——为每个场景创建单独的枚举值/子类。这允许调用方通过模式匹配(when/switch)而不是字符串比较来处理每种情况。在 Swift 中,这提供了 exhaustive checking——如果 NetworkError 枚举的某个值未被处理,编译器会发出警告。
sealed class NetworkError(val message: String) : Throwable(message) {
data class Timeout(val durationMs: Long) :
NetworkError("Request timed out after ${durationMs}ms")
data class HttpError(val code: Int, val body: String?) :
NetworkError("HTTP $code")
data class NoConnection(val cause: IOException) :
NetworkError("No internet connection")
}
密封类 NetworkError 继承自 Throwable(Kotlin 中的标准异常类型)。每个子类都是带有自己字段的 data class:Timeout 包含以毫秒为单位的超时持续时间,HttpError 包含代码和响应体,NoConnection 包含原始 IOException。这种设计允许通过 when 进行穷举处理(当添加新的子类时,编译器将强制更新所有 when 表达式)。
Swift 提供了三种调用 throws 函数的方式,每种都有其自己的安全合约。try 是标准方式:需要 do-catch 或在 throws 函数内部。try? 将错误转换为 nil——结果变为可选,出现错误时返回 nil,类型从 T 变为 T?。try! 是强制执行而不处理:如果抛出错误,应用程序将崩溃。仅在绝对确定错误不可能发生时使用 try!(例如,确保正确的数据)。
let configPath = Bundle.main.path(forResource: "config", ofType: "json")!
// try? — 可选结果
let data = try? Data(contentsOf: URL(fileURLWithPath: configPath))
let json = try? JSONSerialization.jsonObject(with: data ?? Data())
// try! — 保证成功(仅在确定时使用)
let decoder = JSONDecoder()
let defaultConfig = try! decoder.decode(
Config.self,
from: Config.defaultJSON
)
// try — 标准处理
do {
let user = try fetchUser()
showUser(user)
} catch let error as NetworkError {
showRetryAlert(error.message)
}
在示例中,try! 用于内置于应用程序包中的预先验证的 JSON——在正确发布的情况下解码错误不可能发生。try? 用于读取配置文件——如果文件丢失或损坏,应用程序使用默认值而不是崩溃。try 在 do-catch 中用于网络请求,其中错误是可预期的并需要用户响应。建议:避免在生产代码中使用 try!——仅对构建时验证的常量数据使用。
常见问题
Throw 是在程序执行期间抛出错误的运算符,中断执行流程。Throws 是函数签名的修饰符,声明该函数可能抛出错误。没有 throws 的函数不能使用 throw。Throws 是编译时合约,throw 是运行时操作。
Kotlin 遵循 unchecked exceptions 的理念:所有异常都可以在不进行语法强制的情况下不处理。Kotlin 开发人员认为 Java 中的 throws 导致过多的 try-catch 块以及通过空的 catch 忽略 checked exceptions。Kotlin 的 Nothing 类型允许将 throw 用作表达式,以更灵活的方式替代 throws。
try! 仅在您绝对确定错误不可能发生时使用:来自 bundle 的预先验证的 JSON、常量数据、正确的 URL 方案。在生产代码中,try! 是例外而非规则。对于带有回退值的可选场景,try? 更可取,而 try 与 do-catch 用于强制错误处理。
Rethrows 是用于接受 throws 闭包的函数的修饰符。带有 rethrows 的函数仅在传递的闭包抛出错误时才会抛出错误。这允许高阶函数(map、filter)同时处理 throws 和非 throws 闭包,而无需在调用方强制使用 try。
可以,在 catch 内部可以使用 throw 将错误向上传播到栈中,将其包装为另一种类型或添加上下文。这称为 error chaining 或 rethrow。在 Swift 中,只需在 catch 内部再使用一个 throw,在 Kotlin 中则在 catch 块内部使用 throw。finally 块会在控制权继续传递之前执行。
总结
我们将开发一款交钥匙移动应用程序
IT Sectr自2017年以来为初创企业和企业打造iOS和Android应用程序。我们将为您提供咨询并提出最佳解决方案。