Reflection - HimxhMC/HimDSL GitHub Wiki
反射与类解析 / Reflection and Class Resolution
什么时候涉及 / When It Matters
写脚本时,你很少需要关心类是怎么找到的。但下面两种情况会出现问题:
You rarely need to think about how classes are located. Two situations break the silence:
-
引用了简单名但 HimDSL 找不到。
-
引用的类在不同包下有重名,拿到的不是你想要的那个。
-
A simple name fails to resolve.
-
A class exists in multiple packages, and you get the wrong one.
这一章讲清楚解析规则,让你知道该写什么、不该写什么。
This chapter lays out the resolution rules, so you know what to write and what to avoid.
两种类名写法 / Two Name Forms
Material.DIAMOND // 简单名
org.bukkit.Material.DIAMOND // 全限定名
两种写法都合法。HimDSL 的解析顺序不同:
Both forms are valid. The resolution differs:
-
全限定名:直接按名字加载,优先尝试。
-
简单名:在预设的默认包里按顺序查找。
-
Fully-qualified: loads by exact name directly.
-
Simple: searches a preset list of packages in order.
默认先试全限定名——如果类名里含 .,会当作全路径尝试;失败再回退到简单名逻辑。
FQN is tried first — names containing . are loaded directly; failure falls back to simple-name logic.
简单名的查找范围 / Where Simple Names Are Searched
HimDSL 内置了一份包清单,简单名会依次在这些包里找:
HimDSL carries a built-in package list; simple names are searched in order:
org.bukkit
org.bukkit.attribute
org.bukkit.block
org.bukkit.block.biome
org.bukkit.block.data
org.bukkit.block.sign
org.bukkit.enchantments
org.bukkit.entity
org.bukkit.event
org.bukkit.event.block
org.bukkit.event.enchantment
org.bukkit.event.entity
org.bukkit.event.hanging
org.bukkit.event.inventory
org.bukkit.event.player
org.bukkit.event.raid
org.bukkit.event.server
org.bukkit.event.vehicle
org.bukkit.event.weather
org.bukkit.event.world
org.bukkit.inventory
org.bukkit.inventory.meta
org.bukkit.loot
org.bukkit.map
org.bukkit.permissions
org.bukkit.potion
org.bukkit.scoreboard
org.bukkit.util
找到第一个匹配就停,不会继续找后面的包。
Stops at the first hit.
事件类型的查找 / Event Type Resolution
事件类型走不同的包顺序(见 EventManager):
Event types use a different order (see EventManager):
org.bukkit.event.player.
org.bukkit.event.block.
org.bukkit.event.entity.
org.bukkit.event.inventory.
org.bukkit.event.server.
org.bukkit.event.world.
org.bukkit.event.
这个顺序优先玩家相关事件——大多数脚本处理的是玩家动作。
This order prioritizes player events — most scripts handle player actions.
缓存机制 / Caching
HimDSL 对类解析做两级缓存:
HimDSL caches at two levels:
-
正向缓存:成功解析的类记下,下次直接用。
-
负向缓存:解析失败的简单名/全路径也记下,不会重复尝试。
-
Positive cache: successful resolutions are remembered.
-
Negative cache: failed resolutions are also remembered; no retries.
负向缓存有个副作用:第一次解析失败后,同名引用会立即失败。如果你在脚本运行期间动态加载了类,HimDSL 不会感知——缓存已记住"找不到"。
Negative caching has a side effect: after the first failure, the same name fails instantly. If a class becomes available at runtime, HimDSL does not notice — the cache still says "not found".
日常脚本里这不会出问题。类加载是编译期/插件启动时的事,不涉及运行期动态。
Daily scripts are unaffected. Class loading happens at plugin startup, not at runtime.
优先用简单名还是全限定名 / Simple vs FQN
日常场景用简单名:
Use simple names day-to-day:
Material.DIAMOND
GameMode.CREATIVE
PlayerJoinEvent
短、清晰、约定俗成。
Short, clear, conventional.
下面三种情况改用全限定名:
Use FQN in three cases:
情况一:简单名找不到
第三方插件的类不在默认包里,必须写全路径:
Third-party plugin classes are not in the default packages; write the full path:
com.example.customplugin.CustomEvent
情况二:重名冲突
同一个简单名在多个包下存在时,简单名只取第一个匹配。想指定另一个包,用全限定名。
With duplicate simple names, the first match wins. Force another package with FQN.
情况三:需要突出"就是这个类"
关键逻辑里写全限定名,让读者一眼确认是哪个类:
In critical logic, FQN makes the exact class explicit:
org.bukkit.Material.DIAMOND
日常脚本不需要这种严谨。只有阅读者可能误认时才用。
Not needed in daily scripts. Only when readers could misidentify.
静态字段与静态方法 / Static Fields and Methods
解析出类之后,字段和方法访问规则:
After a class resolves:
Material.STONE // 静态字段
Material.values() // 静态方法
Material.valueOf("STONE") // 静态方法带参
必须是 public static。非静态的字段或方法通过类名访问会报错。
Must be public static. Non-static members accessed through the class name error.
// ✗ 例如 getName() 是实例方法
Material.getName() // 报错
想访问实例方法,需要有一个实例:
For instance methods, you need an instance:
var stone = Material.STONE;
string name = stone.name();
一个常见错误 / A Common Mistake
var e = PlayerJoinEvent; // ✗ 事件类不是值,不能这样引用
事件类(PlayerJoinEvent)只用在 event(...) 参数里,不能当成静态引用:
Event classes (PlayerJoinEvent) only appear in event(...) parameters; they are not static references:
@EventHandler
void onJoin(event(PlayerJoinEvent)) { // ✓
// ...
}
类(Material、GameMode)才有静态字段和方法:
Classes (Material, GameMode) have static fields and methods:
var d = Material.DIAMOND;
区分方法:类名在你写代码时会联想到"有哪些常量"(Material、GameMode、Sound),事件类会联想到"什么时候发生"(PlayerJoinEvent)。
Distinguish by intuition: classes make you think of constants (Material, GameMode, Sound); events make you think of timing (PlayerJoinEvent).
小结 / Summary
-
全限定名直接加载;简单名按预设包顺序查找。
-
简单名找到第一个匹配就停。
-
事件类型有独立的包顺序。
-
解析结果正/负双向缓存。
-
日常用简单名;第三方、重名、需要明确时用全限定名。
-
静态访问要求
public static。 -
事件类只用在
event(...),不能当静态引用。 -
FQN loads directly; simple names search preset packages.
-
Simple names stop at the first hit.
-
Event types have their own order.
-
Positive and negative caches.
-
Simple names day-to-day; FQN for third-party, clashes, or explicitness.
-
Static access requires
public static. -
Event classes only appear inside
event(...).
下一步 / Next Steps
下一章 [枚举解析机制 / Enum-Resolution] 讲 Material.DIAMOND 这类表达式怎么被"最长匹配"处理。
Next, [枚举解析机制 / Enum-Resolution] explains how expressions like Material.DIAMOND are resolved by longest match.