本文档面向使用 LuaJavaNE 的 Lua 开发者,介绍如何在 Lua 脚本中无缝调用 Java 类库、对象、数组和异步任务。
所有示例均假设已通过
local java = require("java")加载了java模块。
在 Lua 脚本中,首先需要引入 java 模块:
local java = require("java")该模块提供了所有与 Java 交互的入口。
使用 java.import(类全名) 获取 Java 类的引用(相当于 Java 的 Class 对象)。
local String = java.import("java.lang.String")
local ArrayList = java.import("java.util.ArrayList")
local HashMap = java.import("java.util.HashMap")
local System = java.import("java.lang.System")注意:类名必须使用全限定名(包名 + 类名),例如 "java.lang.String"。
导入的类本身可被当作构造函数调用,使用 类:new(参数...) 或直接 类(参数...) 创建实例。
-- 两种写法等价
local s1 = String:new("Hello")
local s2 = String("World") -- 直接调用
local list = ArrayList()
list:add("a")
list:add("b")如果构造器需要多个参数,直接传入即可,Lua 类型会自动转换为对应的 Java 类型(见后文“类型映射”)。
通过 对象:方法名(参数...) 调用实例方法。
local s = String("LuaJavaNE")
print(s:length()) -- 输出: 9
print(s:substring(0, 4)) -- 输出: "LuaJ"
print(s:indexOf("Java")) -- 输出: 3重载方法:LuaJavaNE 会根据参数类型和数量自动匹配最合适的重载版本。
通过 类.方法名(参数...) 调用静态方法,也支持冒号形式 类:方法名(参数...)。
local System = java.import("java.lang.System")
local Math = java.import("java.lang.Math")
-- 获取系统属性(点号或冒号均可)
local version = System.getProperty("java.version")
print(version)
-- 数学函数
local pi = Math.PI -- 获取常量(静态字段,无需括号)
local maxVal = Math:max(10, 20) -- 冒号形式调用静态方法注意:静态方法和静态字段既可用 类.方法(...) 也可用 类:方法(...)。
冒号形式传入的类本身会被自动识别并剥离,不会作为实参参与签名匹配,因此 Math:max(10, 20) 与 Math.max(10, 20) 等价。
直接使用 对象.字段名 读取或赋值。
local p = java.import("java.awt.Point"):new(10, 20)
print(p.x) -- 输出 10
p.x = 100
print(p.x) -- 输出 100通过类对象访问。
local Math = java.import("java.lang.Math")
print(Math.PI) -- 输出 3.1415926535898同样可赋值(如果字段不是 final)。
注意:对象访问时方法优先于字段——当对象同时存在同名方法和字段时,
对象:名字会调用方法而非读取字段(如BigDecimal.scale()调用的是方法,而不是读取其私有 int 字段)。仅当不存在同名方法时才按字段读取。
使用 java.newArray(类型名, 长度) 创建基本类型或对象数组。
支持的 类型名(仅以下四种,其他类型名会报 unsupported array type):
"int"/"java.lang.Integer""double"/"java.lang.Double""boolean"/"java.lang.Boolean""String"/"java.lang.String"
-- 创建 int 数组
local intArr = java.newArray("int", 5)
intArr[0] = 10 -- 索引从 0 开始(与 Java 一致)
intArr[1] = 20
print(intArr[0]) -- 输出 10
print(#intArr) -- 输出 5(数组长度)
-- 创建 String 数组
local strArr = java.newArray("String", 3)
strArr[0] = "a"
strArr[1] = "b"索引:数组索引从 0 开始(与 Java 一致),越界访问(idx < 0 或 idx >= 长度)会抛出 array index out of bounds 错误;#arr 返回数组长度。
行为统一:Java 方法返回的数组(如 String[]、int[])与 java.newArray 创建的数组使用完全相同的 Java.Array 包装,同样支持 0 基索引、# 长度和元素读写。元素读取时:int/double/boolean 基本类型转为对应的 Lua 值,String 转为 Lua 字符串,其他对象元素包装为 Java 对象 userdata。
错误处理:长度 为负数时返回 nil 与 array size must be >= 0;不支持的 类型名 返回 nil 与 unsupported array type: 类型名。
Lua 的 table 与 Java 的容器/引用之间支持惰性活引用互调:传递的是原对象的引用而非拷贝,一侧的读写另一侧实时可见。
Lua 的 table 可直接作为 Java 参数传入,Java 侧以 com.luajava.LuaTable 接收。LuaTable 持有 Lua 注册表引用,get/put/size/keys 每次经 JNI 直接读写原表。
-- Lua 侧:调用接收 LuaTable 的 Java 方法
local t = { name = "lua", n = 5 }
TestBridge.analyze(t) -- Java 读取字段
TestBridge.touch(t) -- Java 原地修改
print(t.x) -- 50(Java 写入后 Lua 立即可见)// Java 侧
public static void analyze(LuaTable t) {
System.out.println(t.get("name")); // "lua"
System.out.println(t.size()); // 键数
}
public static void touch(LuaTable t) { t.put("x", 50); }
public static long countKeys(LuaTable t) {
return (long) t.keys().length; // 遍历键
}Java 方法返回(或 @LuaFunction 模块方法返回)的 java.util.Map / List,会包装成带 __index/__newindex/__len/__pairs 元表的惰性 userdata,Lua 可像普通表一样读取、遍历、取 # 长度、写回。
local m = TestBridge.makeMap() -- 返回 Map
print(m.a) -- 读取字段
print(#m) -- Map 取 entry 数量
for k, v in pairs(m) do print(k, v) end -- 遍历
m.d = 99 -- 写回,Java 侧 Map 实时更新
local l = TestBridge.makeList() -- 返回 List
print(l[1]) -- 读取(下标从 1 开始)
print(#l) -- 长度
for i, v in ipairs(l) do print(i, v) end
l[2] = "changed" -- 写回
table.insert(l, "x") -- 追加(等价 List.add)
l[#l + 1] = "y" -- 追加:l[#l+1] = v 等价 add写回规则:下标在 1..#l 内为覆盖(List.set),#l + 1 为追加(List.add);写入 #l + 1 之外会明确报错 list index out of bounds: N (size M)。读取越界返回 nil(用于支撑 # 与 ipairs 终止)。
注意:
Java.Array(java.newArray)索引从 0 开始(与 Java 一致);而List是下标从 1 开始的语义容器(Lua 风格)。两者不同。
Lua 函数可存进 Java 容器,读回仍是可直接调用的 Lua 函数:存入 Map / List(或经 LuaTable.put 放回表)的 Lua 函数以注册表引用(LuaFunctionObj)持有,读回时还原为原函数。
local m = TestBridge.makeMap()
m.fib = function(n) -- 存进 Java Map
if n < 2 then return n end
return m.fib(n - 1) + m.fib(n - 2) -- 读回并调用
end
print(m.fib(10)) -- 55
local l = TestBridge.makeList()
l[1] = function(a, b) return a * b end -- 存进 Java List
print(l[1](6, 7)) -- 42Java 对象可直接调用(__call):obj(args...) 会调用其实例方法 call(args...),例如 java.util.function.Function 风格的代理对象;对象没有 call 方法时明确报错 method not found: call。
-- 假设 proxy 是实现 Function 的代理(handler 里有 call 函数)
print(proxy(3)) -- 等价 proxy:call(3)- Lua
table(作参数/返回值)→com.luajava.LuaTable惰性引用 - Java
Map/List/Collection(作返回值)→ Lua 惰性容器 userdata - 重载方法评分:table 参数优先匹配
LuaTable;容器参数可匹配Map/Collection接口
使用 java.createProxy({接口名列表}, handler表) 创建 Java 代理对象,其中 handler 表需包含对应接口方法的 Lua 函数。接口方法被调用时派发到 handler 表中**同名(区分大小写)**的 Lua 函数,第一个参数 self 为 handler 表。
错误处理:接口名列表 必须是非空的字符串数组——列表为空(含误把 handler 表当第一个参数传入,即 java.createProxy(handler, {接口...}))时返回 nil 与 invalid interface list ... 错误信息;接口名不是字符串时返回 interface name at index N is not a string;接口类不存在时返回 interface not found: 类名。以上错误均以 nil + 错误信息 的形式返回,不会导致进程崩溃。
local ran = false
local proxy = java.createProxy({"java.lang.Runnable"}, {
run = function(self) ran = true end
})
-- 在后台线程中运行
local Thread = java.import("java.lang.Thread")
local t = Thread:new(proxy)
t:start()
-- 注意:不要在持有 Lua 锁期间调用 t:join()——主线程持锁等待,
-- 而后台线程的 run() 回调也需要这把锁,会互相等待导致死锁。
-- 等待期用 java.yield 短暂释放 Lua 锁,子线程才有机会执行回调。
local waited = 0
while not ran and waited < 3000 do
java.yield(10)
waited = waited + 10
end类型转换:
- 参数:
String、整数、浮点、布尔、字符转换为对应的 Lua 值;null转为nil;Java 数组包装为Java.Arrayuserdata;其他 Java 对象包装为 Java 对象 userdata(可继续调用其方法)。 - 返回值:
String、整数、浮点、布尔、null转换为对应的 Lua 值;其他对象包装为 userdata(可继续调用其方法)。
完整可运行示例见 examples/proxy.lua。
LuaJavaNE 提供了一套基于 Promise 的异步任务系统,可在后台线程池执行 Java 方法,结果通过轮询取回。
local id = java.promise() -- 返回一个整数 ID每个 id 关联一个可等待的异步结果。
java.runAsync(id, "类名", "方法名", 参数1, 参数2, ...)示例:
local id = java.promise()
java.runAsync(id, "java.lang.Integer", "parseInt", "123")java.runAsyncObj(id, 对象, "方法名", 参数1, 参数2, ...)示例:
local s = java.import("java.lang.String")("Hello")
local id = java.promise()
java.runAsyncObj(id, s, "length")通过 "new" 方法名构造对象:
local id = java.promise()
java.runAsync(id, "java.lang.String", "new", "Hello World")local done, result1, result2, ... = java.checkPromise(id)done:布尔值,true表示已完成。- 后续参数是返回值(可能是多个)。
轮询示例:
repeat
local done, val = java.checkPromise(id)
until done
print(val) -- 打印 "123"如果异步任务返回一个 Java 对象(例如构造器返回),checkPromise 会返回一个 对象 ID(整数),你需要通过 java.getObject(id) 将其转换为 Lua 可用的 Java 对象 userdata。
local id = java.promise()
java.runAsync(id, "java.lang.String", "new", "AsyncString")
repeat
local done, oid = java.checkPromise(id)
until done
local obj = java.getObject(oid) -- obj 现在是一个 Java String 对象
print(obj:length()) -- 输出 11如果异步任务抛出异常,checkPromise 会返回错误字符串(以 "E:" 开头,内容为 类名.方法 -> 异常类型: 消息,已包含根因)。
local id = java.promise()
java.runAsync(id, "java.lang.NonExistentClass", "foo")
local done, err = java.checkPromise(id)
if err and string.sub(err, 1, 2) == "E:" then
print("Error: " .. string.sub(err, 3))
end除轮询外,也可注册完成回调,任务完成时由后台线程自动调用,无需轮询:
local id = java.promise()
java.runAsync(id, "java.lang.Integer", "parseInt", "42")
java.onComplete(id, function(err, result)
if err then
print("失败:", err) -- err 为错误信息字符串
else
print("结果:", result) -- 42
end
end)- 回调签名:
callback(err, result...),err == nil表示成功,result...与checkPromise返回值一致。 - 若任务已完成再注册,会立即触发。
- 回调在后台工作线程执行,应快速返回;耗时的重活请再次
runAsync提交。
主线程轮询等待异步结果或代理回调时,java.yield(ms) 会短暂释放 Lua 锁(默认 10ms)再重新获取,让后台工作线程有机会执行回调,避免"主线程持锁等待 → 工作线程无法执行 Lua"的死锁(详见第 9 节)。
while not done do
java.yield(10) -- 默认 10ms
endjava.store 和 java.fetch 提供了跨多个 LuaRuntime 实例共享数据的机制(基于进程内全局哈希表)。
-- 存储
java.store("myKey", 42)
java.store("greeting", "Hello")
-- 读取
local val = java.fetch("myKey") -- 返回 42
local msg = java.fetch("greeting") -- 返回 "Hello"
-- 列出所有键值对(返回 key -> value 表)
for k, v in pairs(java.listall()) do
print(k, v) -- myKey 42 / greeting Hello
end
-- 删除
java.deleteStore("myKey")支持的类型:nil, number, string, boolean。存储的值会在进程生命周期内保留(除非显式删除)。
java.listall() 返回当前进程存储的瞬态快照表,不支持的复杂类型(table/function/userdata 等)存入时会被降级为 nil 并在 listall() 中跳过。
| Lua 类型 | Java 类型 | 说明 |
|---|---|---|
nil |
null |
传递空引用 |
boolean |
boolean / java.lang.Boolean |
自动拆装箱 |
number (整数) |
int, long(视范围) |
Lua 整数若超出 int 范围则用 long |
number (浮点) |
double / float |
浮点数优先作为 double,可匹配 float |
string |
java.lang.String |
自动转换 |
table (用作参数) |
com.luajava.LuaTable(惰性活引用) |
直接传递,Java 以 LuaTable 接收,读写实时生效(见第 8 节) |
userdata (Java 对象) |
对应 Java 对象 | 保持原引用 |
function |
不直接支持,可用代理封装 | 可通过 java.createProxy 包装为接口 |
返回值转换:
- Java
void→ Luanil - Java
String→ Luastring - Java 基本类型(int, double, boolean 等)→ 对应的 Lua 类型
- Java
Map/List/Collection→ Lua 惰性容器 userdata(__index/__newindex/__len/__pairs,见第 8 节) - 其他 Java 对象 → Lua userdata(可继续调用其方法)
Java→Lua 传参(LuaRuntime.callFunction / callFunctionMultiple 传给 Lua 函数的参数,与返回转换保持一致):
- Java
null→ Luanil - Java
String→ Luastring - Java
char/Character→ Lua 单字符字符串 - Java
byte/short/int/long→ Lua 整数;float/double→ Lua 浮点数 - Java 布尔 → Lua
boolean - Java 数组 → Lua
Java.Arrayuserdata(0 基索引、#长度、元素读写) - Java
Map/List/Collection→ Lua 惰性容器 userdata(见第 8 节) - 其他 Java 对象 → Lua Java 对象 userdata(可继续调用其方法)
-
线程安全:异步任务在独立线程池执行,但 Lua 状态本身不是线程安全的。请勿在多个线程中同时操作同一个
LuaRuntime实例(除非外部加锁)。 -
方法重载:LuaJavaNE 会根据参数类型和数量匹配最合适的重载版本;常规签名查找失败时会自动回退到 Java 反射调用(支持任意返回类型,如
java.math.BigInteger/BigDecimal的方法),仍无法匹配才抛出 Lua 错误。 -
资源释放:Java 对象由 JVM GC 管理,但 Lua userdata 会持有 JNI 全局引用,应避免大量临时对象造成内存压力。必要时可显式调用
java.import("java.lang.System"):gc()建议 GC。 -
数组索引:Java 数组在 Lua 中索引从 0 开始(与 Java 一致),
#arr返回数组长度。 -
异步超时:目前没有提供超时机制,可在 Lua 侧用
utils.timer自行实现。 -
调试:可使用
java.toString(对象)获取 Java 对象的字符串表示(等同于 Java 的toString())。
local java = require("java")
-- 导入类
local String = java.import("java.lang.String")
local ArrayList = java.import("java.util.ArrayList")
local System = java.import("java.lang.System")
-- 创建对象
local s = String("Hello from Lua!")
print(s:length()) -- 17
-- 静态方法
print(System:currentTimeMillis())
-- 数组(索引从 0 开始,与 Java 一致)
local arr = java.newArray("int", 3)
arr[0] = 10
arr[1] = 20
arr[2] = 30
for i = 0, #arr - 1 do print(arr[i]) end
-- 异步调用
local id = java.promise()
java.runAsync(id, "java.lang.Integer", "parseInt", "42")
local done, result
repeat
done, result = java.checkPromise(id)
until done
print("Async result:", result) -- 42
-- 全局存储
java.store("counter", 100)
print(java.fetch("counter")) -- 100- 项目主页:https://github.com/npp-zep/LuaJavaNE
- Java 侧 API 文档(LuaRuntime 等)见
docs/目录或源码注释。
本文档对应 LuaJavaNE 版本 2.2.6.1。