首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> Java 程式設計 >> Java 綜合教學

Java 單例模式實戰:Swing 多面板切換無需重複創建 JFrame

     編輯:Java 綜合教學

Java 單例模式實戰:Swing 多面板無縫切換

在 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; }
}

優點:類加載時即完成初始化,天然線程安全;缺點:若實例從未使用會造成記憶體浪費。

雙重檢查鎖(DCL,推薦生產環境)

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;
    }
}

Swing 單例主窗體 + 面板切換完整實現

1. 主窗體 SingletonFrame(餓漢式單例)

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();    // 重繪
    }
}

2. 面板一 PanelOne(持有主窗體引用,懶漢式單例)

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;
    }
}

3. 面板二 PanelTwo(結構同 PanelOne)

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;
    }
}

4. 啟動類

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 替代方案:對於簡單場景,直接使用 CardLayout 管理多面板更輕量,無需手寫單例。
  • 現代化 UI 框架:新項目建議考慮 JavaFX(Stage/Scene 切換)或基於 Web 技術的 Electron、SwingNode 等方案。

總結

通過「主窗體單例 + 面板單例 + 統一切換入口」模式,能以極少代碼實現 Swing 多界面無縫切換,同時保持面板狀態、避免重複創建開銷。本文提供的 DCL 單例寫法兼顧線程安全與性能,可直接應用於生產級桌面應用開發。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved