第3章 - 错误处理
嗨,朋友!我是长安。
Java 的异常处理你肯定很熟悉:try-catch-finally。Lua 的错误处理机制不太一样,它用的是 pcall 和 xpcall。这一章我们来看看它们怎么用。
🤔 Lua 的错误机制
| 概念 | Java | Lua |
|---|---|---|
| 抛出错误 | throw new Exception() | error("消息") |
| 捕获错误 | try { } catch { } | pcall() / xpcall() |
| 断言 | assert(condition) | assert(condition) |
| 错误类型 | Exception 类体系 | 任意值(通常是字符串) |
💥 error() — 抛出错误
-- 抛出一个错误(类似 Java 的 throw)
error("出错了!")
-- 带错误级别
error("出错了!", 1) -- 级别1:当前函数(默认)
error("出错了!", 2) -- 级别2:调用者
error("出错了!", 0) -- 级别0:不添加位置信息
-- error 可以抛出任意类型的值
error({code = 404, message = "Not Found"})
// Java 等价
throw new RuntimeException("出错了!");
throw new NotFoundException("Not Found");
✅ assert() — 断言
-- 如果条件为假,则抛出错误
local function divide(a, b)
assert(b ~= 0, "除数不能为零!")
return a / b
end
print(divide(10, 2)) -- 5.0
print(divide(10, 0)) -- 错误:除数不能为零!
// Java 等价
public double divide(double a, double b) {
if (b == 0) throw new IllegalArgumentException("除数不能为零!");
return a / b;
}
🛡️ pcall() — 保护调用
pcall(protected call)是 Lua 的 try-catch:
-- pcall 返回:成功标志, 返回值/错误信息
local ok, result = pcall(function()
return 10 / 2
end)
print(ok, result) -- true 5.0
-- 捕获错误
local ok, err = pcall(function()
error("出错了!")
end)
print(ok, err) -- false xxx:2: 出错了!
-- 实际应用:安全地调用可能出错的函数
local function riskyOperation()
-- 可能出错的代码
local file = assert(io.open("not_exist.txt", "r"))
return file:read("*a")
end
local ok, result = pcall(riskyOperation)
if ok then
print("成功: " .. result)
else
print("失败: " .. result)
end
// Java 等价
try {
String content = readFile("not_exist.txt");
System.out.println("成功: " + content);
} catch (Exception e) {
System.out.println("失败: " + e.getMessage());
}
🔧 pcall 传递参数
local function divide(a, b)
if b == 0 then
error("除数不能为零")
end
return a / b
end
-- pcall 可以传递参数给被调用函数
local ok, result = pcall(divide, 10, 2)
print(ok, result) -- true 5.0
local ok, err = pcall(divide, 10, 0)
print(ok, err) -- false xxx:3: 除数不能为零
🔍 xpcall() — 带错误处理器的保护调用
xpcall 比 pcall 多一个参数:错误处理函数(类似 Java 的 catch 块中获取堆栈信息)。
-- 错误处理函数(获取堆栈信息)
local function errorHandler(err)
print("=== 错误详情 ===")
print("错误信息: " .. tostring(err))
print("堆栈跟踪:")
print(debug.traceback("", 2))
print("================")
return err
end
local function riskyCode()
local a = nil
return a.b -- 访问 nil 的属性,会出错
end
local ok, err = xpcall(riskyCode, errorHandler)
if not ok then
print("操作失败,已记录错误日志")
end
// Java 等价
try {
riskyCode();
} catch (Exception e) {
System.out.println("=== 错误详情 ===");
System.out.println("错误信息: " + e.getMessage());
e.printStackTrace();
System.out.println("================");
}
📋 pcall vs xpcall
| 特性 | pcall | xpcall |
|---|---|---|
| 捕获错误 | ✅ | ✅ |
| 错误处理函数 | ❌ | ✅ |
| 堆栈信息 | ❌ | ✅ |
| 适用场景 | 简单错误处理 | 需要详细错误信息 |
🔧 实用模式:安全调用封装
-- 封装一个类似 Java try-catch-finally 的函数
local function tryCatch(tryFunc, catchFunc, finallyFunc)
local ok, result = xpcall(tryFunc, function(err)
return err
end)
if not ok then
if catchFunc then
catchFunc(result)
end
end
if finallyFunc then
finallyFunc()
end
return ok, result
end
-- 使用
tryCatch(
function() -- try
print("执行业务逻辑")
error("模拟错误")
end,
function(err) -- catch
print("捕获错误: " .. tostring(err))
end,
function() -- finally
print("清理资源")
end
)
-- 输出:
-- 执行业务逻辑
-- 捕获错误: xxx:3: 模拟错误
-- 清理资源
🔧 实用模式:带重试的函数调用
-- 带重试的安全调用(在网络请求等场景很常用)
local function retryCall(func, maxRetries, delay)
maxRetries = maxRetries or 3
delay = delay or 1
for i = 1, maxRetries do
local ok, result = pcall(func)
if ok then
return result
end
print(string.format("第 %d 次尝试失败: %s", i, tostring(result)))
if i < maxRetries then
-- Lua 标准库没有 sleep,这里用 os.execute 模拟
os.execute("ping -n 2 127.0.0.1 > nul") -- Windows
end
end
error("重试 " .. maxRetries .. " 次后仍然失败")
end
📝 小结
- Lua 用
error()抛出错误,用pcall()/xpcall()捕获错误 pcall类似简单的try-catch,返回ok, result/errorxpcall支持自定义错误处理函数,可以获取堆栈信息assert()用于前置条件检查- Lua 没有
try-catch-finally语法,但可以自己封装
➡️ 下一步
错误处理搞定了!下一章来学习 面向对象编程,看看 Lua 怎么用 Table 实现类和继承。
💪 练习题
- 写一个函数,使用
pcall安全地读取文件内容。 - 使用
xpcall+debug.traceback捕获错误并打印堆栈信息。 - 封装一个
tryCatchFinally工具函数。 pcall和xpcall的区别是什么?
答案提示
local ok, content = pcall(function() return io.open("f","r"):read("*a") end)xpcall(func, function(e) print(debug.traceback(e)) return e end)- 参考本章的
tryCatch示例 xpcall多一个错误处理函数参数,可以在错误发生时获取堆栈信息
