Skip to content

Latest commit

 

History

History
511 lines (368 loc) · 18.4 KB

File metadata and controls

511 lines (368 loc) · 18.4 KB

LuaJavaNE - Lua 端调用 Java API 参考手册

本文档面向使用 LuaJavaNE 的 Lua 开发者,介绍如何在 Lua 脚本中无缝调用 Java 类库、对象、数组和异步任务。

所有示例均假设已通过 local java = require("java") 加载了 java 模块。


1. 加载 Java 模块

在 Lua 脚本中,首先需要引入 java 模块:

local java = require("java")

该模块提供了所有与 Java 交互的入口。


2. 导入 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"。


3. 创建 Java 对象(调用构造器)

导入的类本身可被当作构造函数调用,使用 类:new(参数...) 或直接 类(参数...) 创建实例。

-- 两种写法等价
local s1 = String:new("Hello")
local s2 = String("World")          -- 直接调用

local list = ArrayList()
list:add("a")
list:add("b")

如果构造器需要多个参数,直接传入即可,Lua 类型会自动转换为对应的 Java 类型(见后文“类型映射”)。


4. 调用实例方法

通过 对象:方法名(参数...) 调用实例方法。

local s = String("LuaJavaNE")
print(s:length())          -- 输出: 9
print(s:substring(0, 4))   -- 输出: "LuaJ"
print(s:indexOf("Java"))   -- 输出: 3

重载方法:LuaJavaNE 会根据参数类型和数量自动匹配最合适的重载版本。


5. 调用静态方法

通过 类.方法名(参数...) 调用静态方法,也支持冒号形式 类:方法名(参数...)。

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) 等价。


6. 访问和修改字段

6.1 实例字段

直接使用 对象.字段名 读取或赋值。

local p = java.import("java.awt.Point"):new(10, 20)
print(p.x)   -- 输出 10
p.x = 100
print(p.x)   -- 输出 100

6.2 静态字段

通过类对象访问。

local Math = java.import("java.lang.Math")
print(Math.PI)   -- 输出 3.1415926535898

同样可赋值(如果字段不是 final)。

注意:对象访问时方法优先于字段——当对象同时存在同名方法和字段时,对象:名字 会调用方法而非读取字段(如 BigDecimal.scale() 调用的是方法,而不是读取其私有 int 字段)。仅当不存在同名方法时才按字段读取。


7. 创建 Java 数组

使用 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: 类型名。


8. Table 双向互调(Lua ↔ Java)

Lua 的 table 与 Java 的容器/引用之间支持惰性活引用互调:传递的是原对象的引用而非拷贝,一侧的读写另一侧实时可见。

8.1 Lua table → Java(LuaTable 活引用)

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;     // 遍历键
}

8.2 Java Map/List → Lua(JavaTable 惰性代理)

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 风格)。两者不同。

8.3 table 内函数 & 可调用 Java 对象

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))                        -- 42

Java 对象可直接调用(__call):obj(args...) 会调用其实例方法 call(args...),例如 java.util.function.Function 风格的代理对象;对象没有 call 方法时明确报错 method not found: call。

-- 假设 proxy 是实现 Function 的代理(handler 里有 call 函数)
print(proxy(3))      -- 等价 proxy:call(3)

8.4 类型映射小结

  • Lua table(作参数/返回值)→ com.luajava.LuaTable 惰性引用
  • Java Map / List / Collection(作返回值)→ Lua 惰性容器 userdata
  • 重载方法评分:table 参数优先匹配 LuaTable;容器参数可匹配 Map / Collection 接口

9. 动态代理(Lua 表实现 Java 接口)

使用 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.Array userdata;其他 Java 对象包装为 Java 对象 userdata(可继续调用其方法)。
  • 返回值:String、整数、浮点、布尔、null 转换为对应的 Lua 值;其他对象包装为 userdata(可继续调用其方法)。

完整可运行示例见 examples/proxy.lua。


10. 异步任务 API(Agent V2)

LuaJavaNE 提供了一套基于 Promise 的异步任务系统,可在后台线程池执行 Java 方法,结果通过轮询取回。

10.1 创建 Promise

local id = java.promise()   -- 返回一个整数 ID

每个 id 关联一个可等待的异步结果。

10.2 提交静态方法任务

java.runAsync(id, "类名", "方法名", 参数1, 参数2, ...)

示例:

local id = java.promise()
java.runAsync(id, "java.lang.Integer", "parseInt", "123")

10.3 提交实例方法任务

java.runAsyncObj(id, 对象, "方法名", 参数1, 参数2, ...)

示例:

local s = java.import("java.lang.String")("Hello")
local id = java.promise()
java.runAsyncObj(id, s, "length")

10.4 构造对象并异步返回

通过 "new" 方法名构造对象:

local id = java.promise()
java.runAsync(id, "java.lang.String", "new", "Hello World")

10.5 轮询结果

local done, result1, result2, ... = java.checkPromise(id)
  • done:布尔值,true 表示已完成。
  • 后续参数是返回值(可能是多个)。

轮询示例:

repeat
    local done, val = java.checkPromise(id)
until done
print(val)   -- 打印 "123"

10.6 获取异步构造的对象

如果异步任务返回一个 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

10.7 错误处理

如果异步任务抛出异常,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

10.8 回调消费结果(java.onComplete)

除轮询外,也可注册完成回调,任务完成时由后台线程自动调用,无需轮询:

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 提交。

10.9 释放锁等待(java.yield)

主线程轮询等待异步结果或代理回调时,java.yield(ms) 会短暂释放 Lua 锁(默认 10ms)再重新获取,让后台工作线程有机会执行回调,避免"主线程持锁等待 → 工作线程无法执行 Lua"的死锁(详见第 9 节)。

while not done do
    java.yield(10)          -- 默认 10ms
end

11. 跨 Lua 状态的全局存储(java.store / java.fetch)

java.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() 中跳过。


12. 类型映射(Lua ↔ Java)

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 → Lua nil
  • Java String → Lua string
  • 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 → Lua nil
  • Java String → Lua string
  • Java char / Character → Lua 单字符字符串
  • Java byte / short / int / long → Lua 整数;float / double → Lua 浮点数
  • Java 布尔 → Lua boolean
  • Java 数组 → Lua Java.Array userdata(0 基索引、# 长度、元素读写)
  • Java Map / List / Collection → Lua 惰性容器 userdata(见第 8 节)
  • 其他 Java 对象 → Lua Java 对象 userdata(可继续调用其方法)

13. 注意事项

  1. 线程安全:异步任务在独立线程池执行,但 Lua 状态本身不是线程安全的。请勿在多个线程中同时操作同一个 LuaRuntime 实例(除非外部加锁)。

  2. 方法重载:LuaJavaNE 会根据参数类型和数量匹配最合适的重载版本;常规签名查找失败时会自动回退到 Java 反射调用(支持任意返回类型,如 java.math.BigInteger/BigDecimal 的方法),仍无法匹配才抛出 Lua 错误。

  3. 资源释放:Java 对象由 JVM GC 管理,但 Lua userdata 会持有 JNI 全局引用,应避免大量临时对象造成内存压力。必要时可显式调用 java.import("java.lang.System"):gc() 建议 GC。

  4. 数组索引:Java 数组在 Lua 中索引从 0 开始(与 Java 一致),#arr 返回数组长度。

  5. 异步超时:目前没有提供超时机制,可在 Lua 侧用 utils.timer 自行实现。

  6. 调试:可使用 java.toString(对象) 获取 Java 对象的字符串表示(等同于 Java 的 toString())。


14. 完整示例

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

15. 更多资料


本文档对应 LuaJavaNE 版本 2.2.6.1。