今天在網上看到一篇文章,感覺很好,它講到的是關于構造函數的作用以及類的構造問題,而這是初學者經常會犯甚至是有經驗的程序員偶爾也會犯的錯誤,在此我舉例總結一下,請看下面這段代碼:

public?abstract?class?BaseDlg?extends?JDialog?
{

????public?BaseDlg(Frame?frame,?String?title)?
{
????????super(frame,?title,?true);
????????this.getContentPane().setLayout(new?BorderLayout());
????????this.getContentPane().add(createHeadPanel(),?BorderLayout.NORTH);
????????this.getContentPane().add(createClientPanel(),?BorderLayout.CENTER);
????????this.getContentPane().add(createButtonPanel(),?BorderLayout.SOUTH);
????}


????private?JPanel?createHeadPanel()?
{
????????
?//?創(chuàng)建對話框頭部
????}

????//?創(chuàng)建對話框客戶區(qū)域,交給子類實現
????protected?abstract?JPanel?createClientPanel();


????private?JPanel?createButtonPanel?
{
????????
?//?創(chuàng)建按鈕區(qū)域
????}
}

這個類在有的代碼中工作得很好,但一個同事在使用時,程序卻擲出了一個NullPointerException違例!經過比較,找出了工作正常和不正常的程序的細微差別,代碼片斷分別如下:
一、工作正常的代碼:

public?class?ChildDlg1?extends?BaseDlg?
{
????JTextField?jTextFieldName;

????public?ChildDlg1()?
{
????????super(null,?"Title");
????}

????public?JPanel?createClientPanel()?
{
????????jTextFieldName?=?new?JTextField();
????????JPanel?panel?=?new?JPanel(new?FlowLayout());
????????panel.add(jTextFieldName);
????????
?//?其它代碼
????????return?panel;
????}
????
}
ChildDlg1?dlg?=?new?ChildDlg1();??//?外部的調用

二、工作不正常的代碼:

public?class?ChildDlg2?extends?BaseDlg?
{
????JTextField?jTextFieldName?=?new?JTextField();

????public?ChildDlg2()?
{
????????super(null,?"Title");
????}

????public?JPanel?createClientPanel()?
{
????????JPanel?panel?=?new?JPanel(new?FlowLayout());
????????panel.add(jTextFieldName);
????????
?//?其它代碼
????????return?panel;
????}
????
}
ChildDlg2?dlg?=?new?ChildDlg2();??//?外部的調用

你看出來兩段代碼之間的差別了嗎?對了,兩者的差別僅僅在于類變量jTextFieldName的初始化時間。經過跟蹤,發(fā)現在執(zhí)行
panel.add(jTextFieldName)語句之時,jTextFieldName確實是空值.
當程序創(chuàng)建一個ChildDlg2的實例時,根據super(null,?“Title”)語句,首先執(zhí)行其父類BaseDlg的構造方法;在BaseDlg的構造方法中調用了createClientPanel()方法,這個方法是抽象方法并且被子類ChildDlg2實現了,因此,實際調用的方法是ChildDlg2中的createClientPanel()方法(因為Java里面采用“動態(tài)綁定”來綁定所有非final的方法);createClientPanel()方法使用了ChildDlg2類的實例變量jTextFieldName,而此時ChildDlg2的變量初始化過程尚未進行,jTextFieldName是null值!所以,ChildDlg2的構造過程擲出一個NullPointerException也就不足為奇了。
再來看ChildDlg1,它的jTextFieldName的初始化代碼寫在了createClientPanel()方法內部的開始處,這樣它就能保證在使用之前得到正確的初始化,因此這段代碼工作正常。
解決問題的兩種方式:
通過上面的分析過程可以看出,要排除故障,最簡單的方法就是要求項目組成員在繼承使用BaseDlg類,實現createClientPanel()方法時,凡方法內部要使用的變量必須首先正確初始化,就象ChildDlg1一樣。然而,把類變量放在類方法內初始化是一種很不好的設計行為,它最適合的地方就是在變量定義塊和構造方法中。
在本文的實例中,引發(fā)錯誤的實質并不在ChildDlg2上,而在其父類BaseDlg上,是它在自己的構造方法中不適當地調用了一個待實現的抽象方法。
從概念上講,構造方法的職責是正確初始化類變量,讓對象進入可用狀態(tài)。而BaseDlg卻賦給了構造方法額外的職責。
本文實例的更好的解決方法是修改BaseDlg類:

public?abstract?class?BaseDlg?extends?JDialog?
{

????public?BaseDlg(Frame?frame,?String?title)?
{
????????super(frame,?title,?true);
????????this.getContentPane().setLayout(new?BorderLayout());
????????this.getContentPane().add(createHeadPanel(),?BorderLayout.NORTH);
????????this.getContentPane().add(createButtonPanel(),?BorderLayout.SOUTH);
????}


????/**?*//**?創(chuàng)建對話框實例后,必須調用此方法來布局用戶界面
?????*/

????public?void?initGUI()?
{
????????this.getContentPane().add(createClientPanel(),?BorderLayout.CENTER);
????}


????private?JPanel?createHeadPanel()?
{
????????
?//?創(chuàng)建對話框頭部
????}

????//?創(chuàng)建對話框客戶區(qū)域,交給子類實現
????protected?abstract?JPanel?createClientPanel();


????private?JPanel?createButtonPanel?
{
????????
?//?創(chuàng)建按鈕區(qū)域
????}
}

新的BaseDlg類增加了一個initGUI()方法,程序員可以這樣使用這個類:
ChildDlg?dlg?=?new?ChildDlg();
dlg.initGUI();
dlg.setVisible(true);

總結:
類的構造方法的基本目的是正確初始化類變量,不要賦予它過多的職責。
設計類構造方法的基本規(guī)則是:用盡可能簡單的方法使對象進入就緒狀態(tài);如果可能,避免調用任何方法。在構造方法內唯一能安全調用的是基類中具有final屬性的方法或者private方法(private方法會被編譯器自動設置final屬性)。final的方法因為不能被子類覆蓋,所以不會產生問題。