Java Swing模拟重力时Timer调度报错:空格键释放触发重复调度问题
报错根因
java.util.TimerTask 为一次性实例:一旦被取消、或已经被提交给Timer调度过,就无法再次提交调度,这是你触发报错的核心原因。你当前代码仅在构造函数中初始化了一次flight、gravity两个TimerTask实例,每次按键取消后重复提交必然触发异常。
另外你现有代码还存在两个逻辑问题:
- keyPressed方法末尾的
gravity.cancel()是全局执行的,按下任意按键都会取消重力任务,逻辑错误 - 后台
java.util.Timer线程直接修改Swing组件位置,存在线程安全风险,Swing组件更新必须在事件调度线程(EDT)中执行
修复步骤
- 不复用TimerTask实例,每次调度前重新创建对应任务实例
- 调整按键事件的逻辑,取消多余的全局cancel调用
- 推荐替换为
javax.swing.Timer,天生适配Swing线程模型,不用额外处理线程安全问题 - 增加状态判断,避免任务重复调度
完整修改后代码
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
相关产品推荐
相关产品推荐

