反射与导入类
AchieveMaster 当前 AST 脚本没有删除反射能力。 它只是把旧解释器时代分散的“调类、调方法、取字段、创建对象”入口,统一收口到了 AST + ReflectionHelper 这一层。
这意味着:
- 仍然可以调用 Java 标准库、Bukkit API、其他插件 API
- 仍然可以导入类、构造对象、访问静态字段、走方法链
- 但正式写法已经统一成 AST 语法,不建议继续扩写旧反射命令式语法
当前反射链路
当前实现主要由这几层组成:
| 组件 | 作用 |
|---|---|
ImportClassNode | 处理 导入类 alias = full.class.Name |
VariableNode | 变量解析失败后,继续尝试把名字当导入类或可加载类 |
NewInstanceNode | 处理 new Xxx(...) 构造调用 |
MethodChainNode | 处理 obj.a.b().c、静态访问、实例访问、字段访问 |
ReflectionHelper | 负责类加载、方法匹配、参数转换、缓存、访问权限放开 |
所以现在文档里的“反射”不是额外外挂能力,而是 AST 表达式系统的一部分。
正式支持什么
导入类
正式写法:
text
导入类 Bukkit = org.bukkit.Bukkit
导入类 ArrayList = java.util.ArrayList
导入类 HashMap = java.util.HashMap导入执行后会发生两件事:
- 当前脚本上下文里写入一个别名变量,值是
Class<?> ReflectionHelper运行时缓存里也会记住这组映射
但文档仍然建议每个脚本显式写自己的 导入类,不要依赖别的脚本之前导入过同名别名。
直接使用完整类名
不导入也可以直接写全限定类名:
text
赋值 max = java.lang.Integer.MAX_VALUE
赋值 abs = java.lang.Math.abs(-9)
赋值 list = new java.util.ArrayList()构造对象
当前正式关键字只有 new。
text
导入类 ArrayList = java.util.ArrayList
赋值 list = new ArrayList()
赋值 data = new java.util.HashMap()当前 AST 词法和表达式解析器没有 新建 关键字入口,所以不要把 新建 Xxx() 当正式语法。
实例方法、静态方法、字段与属性
下面这些都支持:
text
导入类 Bukkit = org.bukkit.Bukkit
导入类 IntegerClass = java.lang.Integer
导入类 ArrayList = java.util.ArrayList
赋值 online = Bukkit.getOnlinePlayers().size()
赋值 max = IntegerClass.MAX_VALUE
赋值 list = new ArrayList()
list.add("a")
list.add("b")
赋值 first = list.get(0)
赋值 upper = list.get(1).toUpperCase()
赋值 size = list.size()属性访问规则如下:
- 先尝试
getXxx() - 再尝试
isXxx() - 都没有再尝试同名字段
也就是说:
itemMeta.displayNameplayer.world.namesomeObject.enabled
这类写法本质上都走的是 getter / 字段访问反射链。
方法链
只要中间对象不是 null,就可以继续往后连:
text
赋值 worldName = player.getWorld().getName()
赋值 onlineCount = Bukkit.getOnlinePlayers().size()如果链中某一步结果是 null,而你后面还继续访问,AST 会直接抛脚本异常,不会静默吞掉。
类型转换
当前表达式还支持 Java 风格的强转写法:
text
赋值 text = (String) item.name
赋值 amount = (int) papi.vault_eco_balance
赋值 mapObj = (java.util.Map) someValue支持的基础类型包括:
int/Integerlong/Longfloat/Floatdouble/Double/Numberboolean/BooleanString
对象类型会尝试按可加载类做 isInstance 校验。
类解析顺序
不同入口的细节略有差异,但整体上当前运行时大致按下面的顺序解析类或链式起点:
- 当前脚本上下文里的变量
- 已导入类别名
- 完整类名
- 常见包下的短类名补全
- 指定插件类加载器
- 遍历所有已加载插件的类加载器
其中“常见包短类名补全”只覆盖这些前缀:
java.lang.java.util.org.bukkit.org.bukkit.entity.org.bukkit.inventory.org.bukkit.event.org.bukkit.potion.org.bukkit.plugin.
这意味着:
ArrayList、HashMap、Bukkit这类名字有机会直接被解析- 第三方插件 API 一般仍建议写完整类名,或者先
导入类
单例兜底规则
当你把“类本身”当调用目标,而你访问的成员又不是静态成员时,当前 AST 会尝试自动找实例。
自动尝试顺序是:
INSTANCEinstancegetInstance()inst()
示例:
text
导入类 SomeApi = com.example.SomePluginApi
赋值 result = SomeApi.getValue()如果 getValue() 不是静态方法,但 SomeApi 符合上面的单例模式之一,方法链仍然可能成功。
这也是当前新 AST 保留旧脚本反射兼容能力的关键点之一。
参数匹配与自动转换
当前构造函数匹配、方法匹配都不是简单靠参数个数硬对。 运行时会做一层兼容打分:
- 精确类型匹配优先级最高
- 父子类型可赋值也能匹配
- 数字之间允许自动转换
- 字符串可尝试转数字或布尔
- 支持可变参数
varargs - 会扫描
public和declared方法 / 构造器 - 会尝试
setAccessible(true)
所以这类调用通常都能直接落:
text
赋值 max = java.lang.Math.max(1, 2.5)
赋值 list = new java.util.ArrayList(16)但仍然建议你别过度依赖“模糊匹配”,尤其是重载很多的第三方 API。
类对象与特殊属性
当前方法链里还有两个容易被忽略的入口:
| 写法 | 含义 |
|---|---|
obj.class | 返回对象的 Class<?> |
SomeClass.class | 返回类对象本身 |
array.length | 数组长度特判 |
示例:
text
赋值 typeName = player.class.getName()旧语法兼容
旧反射语法目前仍有一层兼容归一,但只是为了迁移,不是正式推荐写法。
| 旧写法 | 归一后 |
|---|---|
调用 MathUtil.abs(-4) | MathUtil.abs(-4) |
取静态字段 java.lang.Integer.MAX_VALUE | java.lang.Integer.MAX_VALUE |
创建对象 List 引入包 java.util.ArrayList | 导入类 List = java.util.ArrayList |
所以老脚本很多还能继续跑,但新脚本请直接写 AST 正式语法。
常见例子
调 Java / Bukkit API
text
导入类 Bukkit = org.bukkit.Bukkit
赋值 count = Bukkit.getOnlinePlayers().size()
判断 (count >= 50) {
发送消息("&6服务器当前在线人数很多")
}创建 Map 交给集成功能
text
导入类 HashMap = java.util.HashMap
赋值 data = new HashMap()
data.put("player", player.name)
data.put("progress", progress)调第三方插件单例 API
text
导入类 SomeApi = com.example.SomePluginApi
赋值 result = SomeApi.getInstance().doSomething(player.name)使用边界
- 当前正式构造关键字只有
new,没有新建。 - 反射不是空安全调用,中间结果为
null继续链下去会报错。 - 第三方插件类名尽量写完整路径或先导入别名,不要赌“短类名自动解析”。
- 类引用访问非静态成员时,只有符合单例兜底规则才会成功。
- 运行时虽然会尝试放开访问权限,但这不代表所有服务端环境、所有模块封装都一定允许。
使用建议
- 能
导入类就先导入类,不要整段脚本重复写完整类名。 - 能走内置函数或集成函数时,优先走内置函数或集成函数。
- 反射适合补洞、接第三方 API、处理特殊对象,不适合把整套业务都写成 Java API 拼装。
- 需要构造
Map、List、自定义对象时,优先参考 语法总览 里的表达式规则。