You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

BukkitRunnable触发空指针异常,玩家移动/出水时控制台大量报NPE如何解决

代码问题分析及修复方案

核心问题点

  • 成员变量重复初始化:你将afkCountdown、afkCountdownTask两个全局存储用的HashMap放在了PlayerMoveEvent事件方法内部初始化,该事件每秒钟会触发数十次,每次触发都会清空之前存储的所有玩家倒计时数据、任务实例。之前已经启动的定时任务运行时,从空HashMap里取值会直接返回null,拆箱为int类型时就会抛出NPE。
  • 任务未正确取消:当玩家离开区域/水域时,你仅将任务实例从HashMap中移除,没有调用cancel()方法停止定时任务,任务会持续在后台运行,访问已经被删除的键值对触发NPE。
  • 定时任务逻辑缺判空:BukkitRunnable的run方法中,直接调用afkCountdown.get(p.getUniqueId())取值,没有先判断键是否存在,值为null时拆箱直接触发NPE;且倒计时归0的逻辑存在矛盾:先移除键再重新放入,导致后续的取消逻辑永远无法触发。
  • 空世界风险:创建l1、l2、loc三个Location实例时直接调用Bukkit.getWorld("Void"),如果该世界未加载/不存在,会返回null,后续世界判断逻辑也可能触发NPE。

修复方案

第一步:调整成员变量初始化

将两个HashMap的初始化移到类成员定义阶段,不要在事件中重复初始化:

// 直接在定义时初始化,加上final避免被意外赋值
private final HashMap<UUID, Integer> afkCountdown = new HashMap<>();
private final HashMap<UUID, BukkitRunnable> afkCountdownTask = new HashMap<>();

第二步:修复事件逻辑

@EventHandler
public void onPlayerMoveEvent(PlayerMoveEvent e) {
    Player p = e.getPlayer();
    World voidWorld = Bukkit.getWorld("Void");
    // 先判世界是否存在、玩家是否在Void世界,不符合直接返回,减少性能消耗
    if (voidWorld == null || !p.getWorld().equals(voidWorld)) {
        clearPlayerTask(p.getUniqueId());
        return;
    }
    // 先判断是否在液体中
    if (e.getTo() == null || !e.getTo().getBlock().isLiquid()) {
        clearPlayerTask(p.getUniqueId());
        return;
    }
    Location loc = p.getLocation();
    int x = loc.getBlockX(), y = loc.getBlockY(), z = loc.getBlockZ();
    // 合并坐标判断,无需多层嵌套
    if (x > 52 && x <72 && y>80 && y<100 && z>255 && z<275) {
        UUID uuid = p.getUniqueId();
        if (!afkCountdown.containsKey(uuid)) {
            afkCountdown.put(uuid, 5);
            BukkitRunnable task = new BukkitRunnable() {
                @Override
                public void run() {
                    // 先判键是否存在,不存在直接取消任务
                    if (!afkCountdown.containsKey(uuid)) {
                        cancel();
                        return;
                    }
                    int current = afkCountdown.get(uuid) -1;
                    afkCountdown.put(uuid, current);
                    if (current == 0) {
                        p.sendMessage("Testing");
                        clearPlayerTask(uuid);
                    }
                }
            };
            afkCountdownTask.put(uuid, task);
            task.runTaskTimer(plugin, 20, 20);
        }
    } else {
        clearPlayerTask(p.getUniqueId());
    }
}

// 封装统一的任务清理方法,避免重复代码
private void clearPlayerTask(UUID uuid) {
    if (afkCountdownTask.containsKey(uuid)) {
        // 先取消任务再移除
        afkCountdownTask.get(uuid).cancel();
        afkCountdownTask.remove(uuid);
    }
    afkCountdown.remove(uuid);
}

内容的提问来源于stack exchange,提问作者Not_A_Dev

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.09.23 14:15:01