摘要
緩慢的 GUI 是 Java 最大的性能問題之一。這篇 Java 技巧說明如何創建構造速度較快的 GUI,這是通過只構建必要的部件實現的。
緩慢的圖形用戶界面 (GUI) 是人們對 Java 常發的牢騷。盡管構造快速的 GUI 可能既費時又費力,但如果使用我提供的這個類,您只須做少量的工作就可以加快 GUI 的速度。對於徹底的 GUI 改進而言,這只是第一步;但是,這個類是極好的一種解決方案,因為它很簡單。有時這個類對性能的提高足以滿足您的需要,您無須做任何額外的工作。
方法
這種方法很容易表述:直到必要時才構建 GUI 組件。從宏觀上講,這是所有 Java 程序的默認工作方式。畢竟,誰會在初始化時就構造程序的全部可能的框架和窗口呢?但是,我在本文提供的這個類允許您進行更精細、更惰性的構造。
我構建的用來支持 GUI 組件惰性構造的這個實用工具類基於三點經驗。第一點,大多數 GUI 面板都很簡單,並且無需做任何額外的工作就可以很快完成初始化。
將大量 GUI 組件堆積到屏幕上的面板是大多數緩慢初始化的原因。這些面板主要屬於兩種類型,即帶選項卡的面板或滾動面板。
第二點,通常直到要查看 GUI 組件時才需要構造它。第三點,只有在下列方法之一被調用之後 GUI 組件才是可見的:
public void paint (Graphics)
public void paintComponents(Graphics)
public void paintAll (Graphics)
public void repaint ()
public void repaint (long)
public void repaint (int, int, int, int)
public void repaint (long, int, int, int, int)
public void update (Graphics)
創建實用工具面板類的思想是,將大多數 GUI 構建代碼移出初始化塊和構造函數。我們將這些代碼放入一個名為 lazyConstrUCtor() 的函數中。這個函數完成平常由 GUI 構造函數完成的大多數工作,但它本身並不是構造函數。這個實用工具面板類確保每當調用任何繪制或更新方法之前都調用一次 lazyConstructor 方法。
在選項卡面板內使用這個面板類可以使帶選項卡的面板更快地完成構造,因為最初只需構造可見的那個選項卡面板。
代碼
該類的代碼如下所示:
import java.awt.*;
/**
* LazyPanel 是一個抽象基類,它提供將 Panel 對象的置入操作延遲到
* 實際查看該面板時再進行的功能。當使用 CardLayout 和選項卡面板
* 視圖時這個類極為有用,因為它允許根據您的操作構造子視圖,而不必
* 一開始就等待全部構造完成。
*
* 如果子類選擇覆蓋下面的任一個方法,則它們必須保證被覆蓋的方法
* 首先調用父類的方法。這些方法是:
*
* public void paint (Graphics)
* public void paintComponents(Graphics)
* public void paintAll (Graphics)
* public void repaint ()
* public void repaint (long)
* public void repaint (int, int, int, int)
* public void repaint (long, int, int, int, int)
* public void update (Graphics)
*
* 其中的每個方法都確保首先構造面板,然後便將調用轉發給父類。
*
* 如果您要使用這個類,請首先創建它的一個子類,然後盡可能地
* 將子類構造函數中的代碼移到 lazyConstructor 方法中。下面是
* 使用 LazyPanel 的一個示例:
*
*
*
* import java.awt.*;
*
* class BusyPanel extends LazyPanel
* {
* public BusyPanel (int rows, int cols)
* {
* this.rows = rows;
* this.cols = cols;
* }
*
* protected void lazyConstructor()
* {
* setLayout (new GridLayout (rows, cols));
* for (int i = 0; i < rows * cols; ++i)
* {
* add (new Button (Integer.toString (i + startValue)));
* ++startValue;
* }
* }
*
* static private int startValue = 0;
*
* private int rows;
* private int cols;
* }
*
* 您可以這樣使用它:
*
* import java.awt.*;
* import java.awt.event.*;
*
* class TestFrame extends Frame
* {
* public TestFrame ()
* {
* setLayout (new BorderLayout ());
* add (nextButton, "South");
*
* framePanel.setLayout (layout);
* for (int i = 0; i < 30; ++i)
* framePanel.add (new BusyPanel (8, 8), "");
* add (framePanel, "Center");
*
* nextButton.addActionListener (
* new ActionListener()
* {
* public void actionPerformed (ActionEvent event)
* {
* layout.next (framePanel);
* }
* }
* );
*
* setSize (400, 300);
* }
*
* private CardLayout layout = new CardLayout();
* private Button nextButton = new Button ("Next Panel");
* private Panel framePanel = new Panel();
*
* static public void main (String args[])
* {
* (new TestFrame()).show();
* }
* }
*
* 要查看使用 LazyPanel 的優越性,請試著將 lazyConstructor() 方法中
* 的代碼移到 BusyPanel 的構造函數中,重新編譯該類,然後回頭再看本例。
* 啟動時的額外延遲就是 LazyPanel 類要消除的問題。
*
* 這種處理對 swing 同樣有效。只須對 LazyPanel 稍作修改,讓它
* 擴展 JPanel(而不是 Panel)即可。
*/
public abstract class LazyPanel extends Panel
{
// 我只希望調用 lazyConstructor 一次。
private boolean lazyConstructorCalled = false;
// Swing 的某些版本在將組件添加到它們的容器之前
// 調用 paint() 方法。我們不希望在顯示組件之前
// 調用 lazyConstructor。
private boolean isConstructorFinished = false;
/**
* 創建一個 LazyPanel。
*/
protected LazyPanel ()
{
isConstructorFinished = true;
}
public void paint (Graphics g)
{
callLazyConstructor();
super.paint (g);
}
public void paintAll(Graphics g)
{
callLazyConstructor();
super.paintAll (g);
}
public void paintComponents (Graphics g)
{
callLazyConstructor();
super.paintComponents (g);
}
public void repaint ()
{
callLazyConstructor();
super.repaint();
}
public void repaint (long l)
{
callLazyConstructor();
super.repaint (l);
}
public void repaint (int i1, int i2, int i3, int i4)
{
callLazyConstructor();
super.repaint (i1, i2, i3, i4);
}
public void repaint (long l, int i1, int i2, int i3, int i4)
{
callLazyConstructor();
super.repaint (l, i1, i2, i3, i4);
}
public void update (Graphics g)
{
callLazyConstructor();
super.update (g);
}
/**
* 強制調用在子類中實現的 lazyConstructor() 方法。
* 如果多次調用某個給定對象的這個方法,則除第一次
* 調用以外的任何其他調用不會執行任何操作。
*/
public synchronized final void callLazyConstructor()
{
// 以下代碼的大致思想如下所示:
// 1) 檢查該方法是否被成功調用過。
// 如果是,只返回而不執行任何操作。
//
// 2) 否則 ... 調用惰性構造函數。
// 3) 調用 validate 以使所添加的全部組件變為可見的。
// 4) 注意我們已在運行的程序。
if ((lazyConstructorCalled == false) && (getParent() != null))
{
lazyConstructor();
lazyConstructorCalled = true;
validate();
}
}
/**
* 任何子類都必須實現這個方法。本應該
* 出現在子類構造函數中的大多數組件創建
* 代碼將出現在此處。請參閱上面的
* 示例。
*/
abstract protected void lazyConstructor ();
}
使用 LazyPanel
LazyPanel 類的使用很簡單,但您應該注意幾點。
別在 lazyConstructor 中發出異常
對於面板,我們很容易編寫在發生錯誤時發出異常的構造
對於面板,我們很容易編寫在發生錯誤時發出異常的構造