在 Swing 介面即時顯示系統時間(格式 HH:mm:ss),每秒更新一次。核心挑戰:定時觸發 UI 更新,且不阻塞事件調度執行緒 (EDT)。
public class TimeFrame extends JFrame {
private JLabel displayArea;
private static final int ONE_SECOND = 1000;
public TimeFrame() {
displayArea = new JLabel();
add(new JLabel("CurrentTime: "), BorderLayout.WEST);
add(displayArea, BorderLayout.CENTER);
setDefaultCloseOperation(EXIT_ON_CLOSE);
setSize(200, 70);
setLocationRelativeTo(null); // 置中
Timer timer = new Timer();
timer.scheduleAtFixedRate(new TimerTask() {
@Override public void run() {
String time = new SimpleDateFormat("HH:mm:ss").format(new Date());
// Swing 元件更新必須在 EDT,TimerTask 執行緒非 EDT
SwingUtilities.invokeLater(() -> displayArea.setText(time));
}
}, new Date(), ONE_SECOND);
}
public static void main(String[] args) {
SwingUtilities.invokeLater(() -> new TimeFrame().setVisible(true));
}
}
優點:Timer 可共用多個 TimerTask,scheduleAtFixedRate 固定頻率執行,無累積誤差;注意:UI 更新必須用 SwingUtilities.invokeLater() 轉派到 EDT。
public class TimeFrame2 extends JFrame implements Runnable {
private JLabel displayArea;
public TimeFrame2() {
displayArea = new JLabel();
add(new JLabel("CurrentTime: "), BorderLayout.WEST);
add(displayArea, BorderLayout.CENTER);
setDefaultCloseOperation(EXIT_ON_CLOSE);
setSize(200, 70);
setLocationRelativeTo(null);
}
@Override public void run() {
while (true) {
String time = new SimpleDateFormat("HH:mm:ss").format(new Date());
SwingUtilities.invokeLater(() -> displayArea.setText(time));
try { Thread.sleep(1000); } catch (InterruptedException ignored) {}
}
}
public static void main(String[] args) {
TimeFrame2 frame = new TimeFrame2();
frame.setVisible(true);
new Thread(frame).start();
}
}
缺點:每個時鐘佔一條執行緒,sleep 精度低、累積誤差大,難以統一管理。
ScheduledExecutorService scheduler = Executors.newSingleThreadScheduledExecutor();
scheduler.scheduleAtFixedRate(() -> {
String time = new SimpleDateFormat("HH:mm:ss").format(new Date());
SwingUtilities.invokeLater(() -> displayArea.setText(time));
}, 0, 1, TimeUnit.SECONDS);
優點:標準化執行緒池 API、支援關閉、異常處理更完善,Java 5+ 推薦取代 Timer。
public class WorldClock extends JFrame {
private JComboBox<String> zoneBox;
private JLabel displayArea;
private TimeZone currentZone = TimeZone.getDefault();
public WorldClock() {
zoneBox = new JComboBox<>(TimeZone.getAvailableIDs());
zoneBox.addActionListener(e -> currentZone = TimeZone.getTimeZone(
(String) zoneBox.getSelectedItem()));
displayArea = new JLabel();
add(zoneBox, BorderLayout.NORTH);
add(displayArea, BorderLayout.CENTER);
setDefaultCloseOperation(EXIT_ON_CLOSE);
pack(); setLocationRelativeTo(null); setVisible(true);
ScheduledExecutorService scheduler = Executors.newSingleThreadScheduledExecutor();
scheduler.scheduleAtFixedRate(() -> {
SimpleDateFormat fmt = new SimpleDateFormat("EEE d MMM, HH:mm:ss", Locale.ENGLISH);
fmt.setTimeZone(currentZone);
String time = fmt.format(new Date());
SwingUtilities.invokeLater(() -> displayArea.setText(time));
}, 0, 1, TimeUnit.SECONDS);
}
}
關鍵點:SimpleDateFormat 非執行緒安全,每次更新新建實例或用 ThreadLocal;TimeZone.getAvailableIDs() 取得所有时区 ID。
| 程式碼 | 作用 |
|---|---|
setDefaultCloseOperation(EXIT_ON_CLOSE) | 關閉視窗結束 JVM |
setLocationRelativeTo(null) | 視窗置中螢幕 |
pack() | 依元件優先尺寸自動計算視窗大小 |
SwingUtilities.invokeLater(Runnable) | 將 Runnable 排程到 EDT 執行(非 EDT 更新 UI 必須) |
ScheduledExecutorService(或 Timer)定時任務,避免手動 Thread.sleep 循環。SimpleDateFormat 非執行緒安全,多執行緒環境每次新建或用 DateTimeFormatter (Java 8+)。JComboBox 動態切換 TimeZone,pack() 自適應版面。