如何跨类定义并调用列表供BlockBreakEvent监听器使用?
你当前的写法确实存在性能和可维护性问题:每次触发方块破坏事件都会重新创建ArrayList实例、重复添加所有方块元素,事件高频触发时会产生大量无用临时对象增加GC压力,同时方块列表硬编码在事件方法内,后续修改规则需要翻业务逻辑代码,维护成本很高。
注意:你原代码存在逻辑bug:allowedblocks.contains(block)返回true时代表方块在允许列表内,你当前逻辑反而会取消破坏事件,和注释描述的规则相反,后续代码已修正该问题。
方案1:最简实现(适合小型插件,无额外复杂度)
不需要引入复杂框架,直接创建独立类存储全局规则列表即可,列表在类加载时仅初始化一次,全局唯一可直接调用。
首先创建独立的规则常量类:
// 全局方块规则存储类 public final class BlockRules { // 静态不可变列表,类加载时初始化,仅创建一次 public static final List<Material> ALLOWED_BREAK_BLOCKS = List.of( Material.STONE, Material.DIRT, Material.COBBLESTONE // 直接追加需要的方块类型即可,无需重复写add方法 ); // 私有构造方法,禁止实例化工具类 private BlockRules() {} }
修改监听器代码,移除方法内的列表创建逻辑,直接调用全局列表判断:
public class BlockBreakListener implements Listener { @EventHandler public void onBreak(BlockBreakEvent e) { Player player = e.getPlayer(); Material brokenBlock = e.getBlock().getType(); if (BlockRules.ALLOWED_BREAK_BLOCKS.contains(brokenBlock)) { player.sendMessage("有效方块,允许破坏"); return; } player.sendMessage("无效方块,破坏已取消"); e.setCancelled(true); } }
该方案优缺点:
- 优点:代码量最小,无学习成本,完全解决重复创建列表的性能问题,修改规则直接去
BlockRules类调整即可 - 缺点:静态常量耦合度较高,不支持运行时热重载规则,适合规则固定的小型插件
方案2:依赖注入实现(适合中大型插件,支持扩展/热重载)
不需要专门引入第三方DI框架,通过构造方法传参就是最朴素的依赖注入实现,这种方式完全解耦规则存储和事件逻辑,后续支持从配置文件、数据库加载规则,也可以做热重载不用重启服务器。
首先创建独立的规则管理器类,不使用静态变量:
public class BlockRuleManager { private Set<Material> allowedBreakBlocks; public BlockRuleManager() { // 插件启动时初始化一次规则 loadRules(); } private void loadRules() { // 这里既可以硬编码方块,也可以改成从config.yml等配置文件读取 this.allowedBreakBlocks = new HashSet<>(List.of( Material.STONE, Material.DIRT, Material.COBBLESTONE )); } // 提供重载方法,后续可通过命令触发配置热更 public void reloadRules() { loadRules(); } // 对外只提供判断方法,不直接暴露内部集合,避免外部误修改规则 public boolean canBreakBlock(Material material) { return allowedBreakBlocks.contains(material); } }
在插件主类中初始化全局唯一的规则管理器实例,注册监听器时将实例传入(即完成依赖注入):
public class YourPlugin extends JavaPlugin { private BlockRuleManager blockRuleManager; @Override public void onEnable() { // 插件启动时仅初始化一次规则管理器 blockRuleManager = new BlockRuleManager(); // 注册监听器时传入已初始化的管理器实例 getServer().getPluginManager().registerEvents(new BlockBreakListener(blockRuleManager), this); } // 对外提供管理器实例,供重载命令等其他模块调用 public BlockRuleManager getBlockRuleManager() { return blockRuleManager; } }
修改监听器,通过构造方法接收注入的规则管理器实例:
public class BlockBreakListener implements Listener { private final BlockRuleManager blockRuleManager; // 构造方法接收注入的依赖 public BlockBreakListener(BlockRuleManager blockRuleManager) { this.blockRuleManager = blockRuleManager; } @EventHandler public void onBreak(BlockBreakEvent e) { Player player = e.getPlayer(); Material brokenBlock = e.getBlock().getType(); if (blockRuleManager.canBreakBlock(brokenBlock)) { player.sendMessage("有效方块,允许破坏"); return; } player.sendMessage("无效方块,破坏已取消"); e.setCancelled(true); } }
该方案优缺点:
- 优点:规则逻辑和事件逻辑完全解耦,列表仅在插件启动/重载时初始化,无额外性能开销;支持热重载配置,后续修改规则加载逻辑不需要改动事件代码,可测试性更强
- 缺点:相比静态常量写法代码量稍高
性能优化提示
如果你的规则列表方块数量超过30个,建议用
HashSet替代ArrayList存储方块类型:HashSet的contains查询时间复杂度为O(1),远快于ArrayList的O(n)遍历查询,事件高频触发时性能差距会非常明显,两种集合的contains方法调用写法完全一致,不需要修改业务判断逻辑。
内容的提问来源于stack exchange,提问作者user19387122

