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

Java Swing模拟重力时Timer调度报错:空格键释放触发重复调度问题

报错根因

java.util.TimerTask 为一次性实例:一旦被取消、或已经被提交给Timer调度过,就无法再次提交调度,这是你触发报错的核心原因。你当前代码仅在构造函数中初始化了一次flight、gravity两个TimerTask实例,每次按键取消后重复提交必然触发异常。
另外你现有代码还存在两个逻辑问题:

  1. keyPressed方法末尾的gravity.cancel()是全局执行的,按下任意按键都会取消重力任务,逻辑错误
  2. 后台java.util.Timer线程直接修改Swing组件位置,存在线程安全风险,Swing组件更新必须在事件调度线程(EDT)中执行
修复步骤
  1. 不复用TimerTask实例,每次调度前重新创建对应任务实例
  2. 调整按键事件的逻辑,取消多余的全局cancel调用
  3. 推荐替换为javax.swing.Timer,天生适配Swing线程模型,不用额外处理线程安全问题
  4. 增加状态判断,避免任务重复调度
完整修改后代码
import java.awt.event.*;
import javax.swing.*;
import java.awt.Color;

public class Game implements KeyListener {
    JFrame frame;
    JLabel player;

    public boolean grounded = false;
    public int velocity = 0;
    public int finalY = 0;
    public boolean spaceHeld;
    
    public Timer flightTimer; // 改用Swing的Timer
    public Timer gravityTimer;

    Game() {
        frame = new JFrame("Nicholas Seow-Xi Crouse");
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.setSize(1920, 1080);
        frame.setLayout(null);
        
        frame.setFocusable(true);
        frame.addKeyListener(this);

        player = new JLabel();
        player.setBackground(Color.red);
        player.setBounds(10, 800, 50, 50);
        player.setOpaque(true);

        frame.add(player);
        frame.setVisible(true);
    }

    // 抽离上升任务逻辑,每次调用返回新的任务实例
    private ActionListener createFlightTask() {
        return e -> {
            if(velocity > -5) {
                velocity -= 1;
            } else {
                velocity = -5;
            }
            if (finalY < -61) {
                finalY = finalY + velocity;
            } else {
                finalY = -61;
            }
            player.setLocation(player.getX(), player.getY() + finalY);
            if (player.getY() <= 0){
                flightTimer.stop();
                velocity = 0;
                finalY = 0;
                player.setLocation(player.getX(), 0);
            }
        };
    }

    // 抽离重力任务逻辑,每次调用返回新的任务实例
    private ActionListener createGravityTask() {
        return e -> {
            if(velocity < 5){
                velocity += 1;
            } else {
                velocity = 5;
            }
            if (finalY < 61) {
                finalY = finalY + velocity;
            } else {
                finalY = 61;
            }
            player.setLocation(player.getX(), player.getY() + finalY);
            if (player.getY() >= 1000){
                gravityTimer.stop();
                velocity = 0;
                finalY = 0;
                player.setLocation(player.getX(), 990);
            }
        };
    }

    public void keyTyped(KeyEvent e) {}

    public void keyPressed(KeyEvent e) {
        if(e.getKeyCode() == KeyEvent.VK_SPACE){
            if(!spaceHeld) {
                // 先停止正在运行的重力任务
                if(gravityTimer != null && gravityTimer.isRunning()) {
                    gravityTimer.stop();
                }
                // 创建新的上升任务并启动
                flightTimer = new Timer(33, createFlightTask());
                flightTimer.start();
                spaceHeld = true;
            }
        }
    }

    public void keyReleased(KeyEvent e) {
        if(e.getKeyCode() == KeyEvent.VK_SPACE){
            spaceHeld = false;
            // 停止上升任务
            if(flightTimer != null && flightTimer.isRunning()) {
                flightTimer.stop();
            }
            // 创建新的重力任务并启动
            gravityTimer = new Timer(33, createGravityTask());
            gravityTimer.start();
        }
    }

    public static void main(String[] args) {
        // Swing初始化必须在EDT执行
        SwingUtilities.invokeLater(Game::new);
    }
}
效果说明

修改后按下空格键启动上升任务,释放空格自动停止上升、启动重力下落任务,直到角色落到屏幕底部自动停止,不会再出现重复调度的报错。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.10.01 01:24:02