本文档面向 Java 开发者,介绍如何在 Java 代码中使用 LuaJavaNE 提供的 API 来执行 Lua 脚本、调用 Lua 函数、以及将 Java 对象暴露给 Lua 环境。
将以下 JAR 文件放入 classpath:
luajava.jar– Java 端核心库lib/jline.jar– 可选,用于 REPL 行编辑支持
同时确保本机动态库 luajava.so(Linux/Android)或 luajava.dylib(macOS)位于系统库路径或通过 -Dluajava.library.path 指定。
import com.luajava.LuaRuntime;
LuaRuntime L = new LuaRuntime();每个 LuaRuntime 实例对应一个独立的 Lua 状态机(lua_State*)。
重要:使用完毕后必须调用 L.close() 释放本机资源,或利用 try-with-resources(实现了 AutoCloseable)。
L.doString("print('Hello from Lua!')");
L.doString("x = 42");L.doFile("/path/to/script.lua");对于需要多次调用的函数,可以预编译为 LuaFunctionObj 以提高性能:
LuaFunctionObj fn = L.compile("return function(a, b) return a + b end");
// 调用编译后的函数(返回一个 Lua 函数对象)
Object[] results = fn.callMultiple(3, 5); // results[0] == 8
fn.destroy(); // 释放本机引用// Lua 中定义:function add(a, b) return a + b end
Object result = L.callFunction("add", 10, 20); // 返回 Integer 30支持参数类型自动转换:Java 的 String、Integer、Double、Boolean 会被映射为 Lua 的对应类型,其他 Java 对象会被包装为 userdata(见第 7 节)。
// Lua 中定义:function swap(a, b) return b, a end
Object[] results = L.callFunctionMultiple("swap", "hello", "world");
// results[0] = "world", results[1] = "hello"若 Lua 函数抛出错误,callFunction 会抛出 LuaRuntimeError(其父类即 RuntimeException),错误信息来自 Lua 栈。
L.setGlobal("name", "LuaJava");
// 对应 Lua: name = "LuaJava"注意:
setGlobal目前仅支持字符串。若需设置其他类型,请通过执行doString完成,或使用注解注册的 Java 方法。
String version = L.getGlobal("_VERSION");
System.out.println(version); // "Lua 5.4.8"通过注解 @LuaModule 和 @LuaFunction,您可以将任意 Java 类的实例方法暴露为 Lua 全局函数。
import com.luajava.LuaModule;
import com.luajava.LuaFunction;
@LuaModule("math") // 模块前缀,可选
public class MyMath {
@LuaFunction
public int add(int a, int b) {
return a + b;
}
@LuaFunction("mul") // 自定义函数名
public int multiply(int a, int b) {
return a * b;
}
}LuaRuntime L = new LuaRuntime();
L.registerModule(new MyMath());
L.doString("print(math_add(3, 5))"); // 输出 8
L.doString("print(math_mul(6, 7))"); // 输出 42- 若未指定
@LuaModule.value(),则使用空前缀,函数名即为方法名(或@LuaFunction.value())。 - 若指定了前缀,则生成全局函数名为
前缀_函数名(例如math_add)。
支持的方法参数类型:int、long、double、float、boolean、String,以及这些类型的包装类。返回值同样支持上述类型,也可以返回任意 Java 对象(会被包装为 userdata)。
LuaRuntime.compile() 返回的 LuaFunctionObj 代表一个已编译的 Lua 函数(可以是闭包)。
LuaFunctionObj fn = L.compile("return function(x) return x * 2 end");
// 调用该函数获得真正的 double 函数
LuaFunctionObj doubler = (LuaFunctionObj) fn.call();
Object result = doubler.call(21); // 42
fn.destroy();
doubler.destroy();call(Object... args)– 返回第一个返回值callMultiple(Object... args)– 返回所有返回值数组
LuaFunctionObj 通过引用计数持有所属的 lua_State:即使先调用 LuaRuntime.close(),状态也会等到最后一个 LuaFunctionObj/LuaInvocationHandler 释放后才真正关闭,因此不会出现"状态已释放仍被引用"导致的崩溃。
- 建议显式调用
destroy()释放本机注册表引用(幂等,可重复调用)。 - 未调用时,对象被 GC 回收会经
finalize()兜底释放,但不推荐依赖 GC 时机。
| Java 类型 | Lua 类型 |
|---|---|
null |
nil |
java.lang.String |
string |
java.lang.Integer / int |
integer |
java.lang.Long / long |
integer |
java.lang.Double / double |
number |
java.lang.Float / float |
number |
java.lang.Boolean / boolean |
boolean |
| 其他对象 | userdata(Java 对象代理) |
| Lua 类型 | Java 返回类型 |
|---|---|
nil |
null |
boolean |
java.lang.Boolean |
integer |
java.lang.Integer |
number |
java.lang.Double |
string |
java.lang.String |
function |
com.luajava.LuaFunctionObj |
table |
暂不支持直接转换(可设计为 Map) |
userdata |
保持原 Java 对象引用(如果是通过 Java 传入的) |
当 Lua 函数返回一个 Java 对象时(如通过 Java 方法返回的对象),该对象会被包装为 userdata,并在 Java 侧以原类型返回。
public interface Greeting {
String sayHello(String name);
}local Greeting = java.import("com.example.Greeting")
local handler = {
sayHello = function(self, name)
return "Hello, " .. name .. " from Lua!"
end
}
local proxy = java.createProxy({"com.example.Greeting"}, handler)// 将 proxy 作为 Object 传入 Java,然后强转为接口
Greeting g = (Greeting) proxy;
System.out.println(g.sayHello("Java")); // Hello, Java from Lua!java.createProxy 的 Lua 用法见 lualibjava.c,Java 侧只需接收 Object 并转型。
LuaJavaNE 提供了一套基于 RuntimeException 的结构化异常层次(包 com.luajava.exception),
既有 catch (RuntimeException) 用法完全向后兼容:
java.lang.RuntimeException
└── LuaJavaException (抽象基类)
├── LuaRuntimeError — Lua 执行期错误(doString/doFile/callFunction 的 pcall 失败)
│ └── LuaSyntaxError — Lua 编译期语法错误(compile(),对应 LUA_ERRSYNTAX)
├── JavaInvocationError — Lua 调用已注册 Java 方法失败时对 checked 异常的包装
└── TypeConversionError — Lua↔Java 参数类型转换失败
- Lua 执行错误会抛出
LuaRuntimeError(语法错误为LuaSyntaxError),错误信息包含 Lua 栈回溯。 - Java 方法通过注解暴露给 Lua 时,若抛出异常会被捕获并转换为 Lua 错误,然后在 Java 侧重新抛出:
方法的
RuntimeException/Error原样重抛,其余 checked 异常统一包装为JavaInvocationError(保留原因)。 - 以上异常均为
RuntimeException的子类,可直接catch (RuntimeException)统一处理。
L.close(); // 释放 lua_State 及所有关联资源若使用 try-with-resources:
try (LuaRuntime L = new LuaRuntime()) {
L.doString("...");
}fn.destroy(); // 释放本机引用(建议显式调用;未调用时 GC 会兜底)LuaJavaNE 内部使用 LuaAgent 管理后台线程池(用于异步任务)。该线程池是全局的,会在 JVM 退出时自动终止。如需主动关闭,可调用:
LuaAgent.shutdown();但通常无需手动操作。
import com.luajava.LuaRuntime;
import com.luajava.LuaFunctionObj;
import com.luajava.LuaModule;
import com.luajava.LuaFunction;
@LuaModule("demo")
class Calculator {
@LuaFunction
public int add(int a, int b) { return a + b; }
}
public class Main {
public static void main(String[] args) throws Exception {
try (LuaRuntime L = new LuaRuntime()) {
// 1. 执行脚本
L.doString("function mul(a, b) return a * b end");
// 2. 调用 Lua 函数
Object product = L.callFunction("mul", 6, 7);
System.out.println("6 * 7 = " + product); // 42
// 3. 注册 Java 模块
L.registerModule(new Calculator());
L.doString("print(demo_add(10, 20))"); // 30
// 4. 编译并调用 Lua 函数
LuaFunctionObj fn = L.compile("return function(x) return x + 100 end");
LuaFunctionObj inc = (LuaFunctionObj) fn.call();
System.out.println(inc.call(50)); // 150
fn.destroy();
inc.destroy();
}
}
}Q:为什么我的 Java 方法没有被注册?
A:确保类上有 @LuaModule 注解,方法上有 @LuaFunction,且方法为 public。
Q:调用 L.doString 时抛异常怎么办?
A:捕获 LuaRuntimeError(语法错误为 LuaSyntaxError,二者均为 RuntimeException 子类)并查看错误信息,通常是 Lua 语法错误或运行时错误。
Q:LuaFunctionObj 必须调用 destroy() 吗?
A:建议调用以尽快释放本机资源。得益于引用计数,即使忘记调用或先关闭了 LuaRuntime,也不会崩溃:底层状态会在最后一个引用释放(含 GC 时的 finalize() 兜底)后才真正关闭。
Q:如何传递数组或集合?
A:目前支持通过 Java 方法返回数组,或使用 java.newArray 在 Lua 中创建 Java 数组,然后传入 Java 方法。
- 项目源码:
java/src/com/luajava/包含所有 Java API 类 - 示例脚本:
examples/目录下的 Lua 示例 - REPL 启动:
./luaj.sh(在项目根目录)
本文档基于 LuaJavaNE v2.2.6.1 编写,如有变动请参考最新源码。