在 Swing 桌面應用開發中,頻繁創建銷毀 JFrame 不僅閃屏體驗差,還浪費資源。本文通過「懶漢式」與「餓漢式」單例模式對比,演示如何將主窗體設計為單例,僅通過 setContentPane() 切換內部 JPanel 實現多界面無縫切換,附帶完整可運行代碼與線程安全分析。
public class SingletonLazy {
private SingletonLazy() { System.out.println("Lazy 初始化"); }
private static SingletonLazy instance = null;
public static SingletonLazy getInstance() {
if (instance == null) instance = new SingletonLazy();
return instance;
}
}
缺點:多線程併發時可能創建多個實例。
public class SingletonEager {
private SingletonEager() {}
private static final SingletonEager INSTANCE = new SingletonEager();
public static SingletonEager getInstance() { return INSTANCE; }
}
優點:類加載時即完成初始化,天然線程安全;缺點:若實例從未使用會造成記憶體浪費。
public class SingletonDCL {
private SingletonDCL() {}
private static volatile SingletonDCL instance = null;
public static SingletonDCL getInstance() {
if (instance == null) {
synchronized (SingletonDCL.class) {
if (instance == null) instance = new SingletonDCL();
}
}
return instance;
}
}
package com.example.swing;
import javax.swing.JFrame;
public class SingletonFrame extends JFrame {
private static final SingletonFrame INSTANCE = new SingletonFrame();
private SingletonFrame() {
setTitle("單例模式窗口切換演示");
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
setBounds(100, 100, 600, 400);
setLocationRelativeTo(null); // 居中顯示
// 默認顯示面板一
setContentPane(PanelOne.getInstance(this));
}
public static SingletonFrame getInstance() {
return INSTANCE;
}
/** 統一面板切換入口,自動刷新 */
public void switchPanel(javax.swing.JPanel panel) {
setContentPane(panel);
revalidate(); // 觸發佈局重計算
repaint(); // 重繪
}
}
package com.example.swing;
import javax.swing.*;
import java.awt.event.*;
public class PanelOne extends JPanel {
private static PanelOne instance = null;
private final JFrame owner;
private PanelOne(JFrame owner) {
this.owner = owner;
setLayout(null);
JLabel label = new JLabel("這是面板一", SwingConstants.CENTER);
label.setBounds(200, 50, 200, 30);
add(label);
JButton btn = new JButton("切換到面板二");
btn.setBounds(220, 120, 160, 30);
btn.addActionListener(e -> owner.switchPanel(PanelTwo.getInstance(owner)));
add(btn);
}
public static PanelOne getInstance(JFrame owner) {
if (instance == null) instance = new PanelOne(owner);
return instance;
}
}
package com.example.swing;
import javax.swing.*;
import java.awt.event.*;
public class PanelTwo extends JPanel {
private static PanelTwo instance = null;
private final JFrame owner;
private PanelTwo(JFrame owner) {
this.owner = owner;
setLayout(null);
JLabel label = new JLabel("這是面板二", SwingConstants.CENTER);
label.setBounds(200, 50, 200, 30);
add(label);
JButton btn = new JButton("返回面板一");
btn.setBounds(220, 120, 160, 30);
btn.addActionListener(e -> owner.switchPanel(PanelOne.getInstance(owner)));
add(btn);
}
public static PanelTwo getInstance(JFrame owner) {
if (instance == null) instance = new PanelTwo(owner);
return instance;
}
}
package com.example.swing;
import javax.swing.SwingUtilities;
public class AppMain {
public static void main(String[] args) {
SwingUtilities.invokeLater(() -> {
SingletonFrame.getInstance().setVisible(true);
});
}
}
| 設計點 | 說明 |
|---|---|
| 主窗體單例 | 全程僅一個 JFrame 實例,避免重複創建開銷與閃屏 |
| 面板單例 | 各功能面板同樣採用單例,保持狀態(如表單輸入、滾動位置)不丟失 |
| 面板持有主窗體引用 | 面板構造器接收 JFrame,通過回調 switchPanel() 實現切換,解耦面板間直接依賴 |
| 統一切換方法 | switchPanel() 封裝 setContentPane() + revalidate() + repaint(),確保佈局即時生效 |
| EDT 線程安全 | 啟動與事件處理均在 Event Dispatch Thread 執行,符合 Swing 線程規範 |
reset() 方法,在 switchPanel() 前調用。CardLayout 管理多面板更輕量,無需手寫單例。Stage/Scene 切換)或基於 Web 技術的 Electron、SwingNode 等方案。通過「主窗體單例 + 面板單例 + 統一切換入口」模式,能以極少代碼實現 Swing 多界面無縫切換,同時保持面板狀態、避免重複創建開銷。本文提供的 DCL 單例寫法兼顧線程安全與性能,可直接應用於生產級桌面應用開發。