Swing 界面顯示動態時間是經典入門案例,核心挑戰:定時任務觸發 + 線程安全更新 UI。本文對比三種方案並提供可直接運行的完整代碼。
Timer timer = new Timer();
timer.scheduleAtFixedRate(new TimerTask() {
@Override
public void run() {
String time = new SimpleDateFormat("HH:mm:ss").format(new Date());
// 必須在 EDT 線程更新 UI
SwingUtilities.invokeLater(() -> timeLabel.setText(time));
}
}, new Date(), 1000); // 立即執行,每 1 秒重複
優點:
scheduleAtFixedRate 保證固定頻率,避免累積誤差注意:TimerTask.run() 在後台線程執行,更新 UI 必須用 SwingUtilities.invokeLater。
new Thread(() -> {
while (true) {
String time = new SimpleDateFormat("HH:mm:ss").format(new Date());
SwingUtilities.invokeLater(() -> timeLabel.setText(time));
try { Thread.sleep(1000); } catch (InterruptedException e) { break; }
}
}).start();
缺點:
sleep 精度低,長期運行有誤差漂移ScheduledExecutorService scheduler = Executors.newSingleThreadScheduledExecutor();
scheduler.scheduleAtFixedRate(() ->
SwingUtilities.invokeLater(() -> timeLabel.setText(new SimpleDateFormat("HH:mm:ss").format(new Date()))),
0, 1, TimeUnit.SECONDS);
優勢:線程池管理、支持延遲/周期、可取消、異常不會導致任務停止(Timer 會)。
import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
import java.text.SimpleDateFormat;
import java.util.*;
public class WorldClock extends JFrame {
private JLabel timeLabel = new JLabel("", SwingConstants.CENTER);
private JComboBox zoneCombo = new JComboBox<>(TimeZone.getAvailableIDs());
private TimeZone currentZone = TimeZone.getDefault();
private final SimpleDateFormat fmt = new SimpleDateFormat("EEE, d MMM yyyy HH:mm:ss z", Locale.ENGLISH);
private final ScheduledExecutorService scheduler = Executors.newSingleThreadScheduledExecutor();
public WorldClock() {
setTitle("世界時鍾");
setDefaultCloseOperation(EXIT_ON_CLOSE);
setLayout(new BorderLayout());
timeLabel.setFont(new Font("Monospaced", Font.BOLD, 24));
add(timeLabel, BorderLayout.CENTER);
zoneCombo.addActionListener(e -> {
currentZone = TimeZone.getTimeZone((String) zoneCombo.getSelectedItem());
fmt.setTimeZone(currentZone);
updateTime(); // 立即刷新
});
add(zoneCombo, BorderLayout.NORTH);
pack();
setLocationRelativeTo(null);
setVisible(true);
scheduler.scheduleAtFixedRate(this::updateTime, 0, 1, TimeUnit.SECONDS);
}
private void updateTime() {
fmt.setTimeZone(currentZone);
String time = fmt.format(new Date());
SwingUtilities.invokeLater(() -> timeLabel.setText(time));
}
public static void main(String[] args) {
SwingUtilities.invokeLater(WorldClock::new);
}
}
| 技術點 | 說明 |
|---|---|
| EDT 線程安全 | 所有 UI 更新必須在 Event Dispatch Thread,使用 SwingUtilities.invokeLater |
| 時區處理 | TimeZone.getAvailableIDs() 獲取所有时区,SimpleDateFormat.setTimeZone() 切換 |
| 格式化模式 | HH:mm:ss 24小時制,hh:mm:ss a 12小時制,z 顯示時區名 |
| 窗口居中 | setLocationRelativeTo(null) |
| 自適應大小 | pack() 根據組件優先大小調整窗口 |
DateTimeFormatter(線程安全)WindowListener 調用 scheduler.shutdown()System.nanoTime() 計算下次執行時間,或引入 Joda-Time / java.time掌握 Swing 定時更新模式,可輕鬆構建數字時鍾、倒計時器、實時監控儀表板等實用工具。