溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

如何通過實例學習Java對象的構造過程

發(fā)布時間:2021-12-03 10:09:38 來源:億速云 閱讀:159 作者:柒染 欄目:編程語言

這篇文章給大家介紹如何通過實例學習Java對象的構造過程,內(nèi)容非常詳細,感興趣的小伙伴們可以參考借鑒,希望對大家能有所幫助。

下面提供一個項目中的錯誤實例,提供對其觀察和分析,揭示出Java語言實例化一個對象具體過程,最后總結出設計Java類的一個重要規(guī)則。通過閱讀本文,可以使Java程序員理解Java對象的構造過程,從而設計出更加健壯的代碼。

作者曾經(jīng)在一個項目里面向項目組成員提供了一個抽象的對話框基類,使用者只需在子類中實現(xiàn)基類的一個抽象方法來畫出顯示數(shù)據(jù)的界面,就可使項目內(nèi)的對話框具有相同的風格。具體的代碼實現(xiàn)片斷如下(為了簡潔起見,省略了其他無關的代碼):

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ū)域,交給子類實現(xiàn)
protected abstract JPanel createClientPanel();

private JPanel createButtonPanel {
... // 創(chuàng)建按鈕區(qū)域
}
}
這個類在有的代碼中工作得很好,但一個同事在使用時,程序卻擲出了一個NullPointerException違例!經(jīng)過比較,找出了工作正常和不正常的程序的細微差別,代碼片斷分別如下:

一、工作正常的代碼:

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(frame, "Title"); // 外部的調(diào)用
二、工作不正常的代碼:

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(); // 外部的調(diào)用
你看出來兩段代碼之間的差別了嗎?對了,兩者的差別僅僅在于類變量jTextFieldName的初始化時間。經(jīng)過跟蹤,發(fā)現(xiàn)在執(zhí)行panel.add(jTextFieldName)語句之時,jTextFieldName確實是空值。

我們知道,Java允許在定義類變量的同時給變量賦初始值。系統(tǒng)運行過程中需要創(chuàng)建一個對象的時候,首先會為對象分配內(nèi)存空間,然后在“先于調(diào)用任何方法之前”根據(jù)變量在類內(nèi)的定義順序來初始化變量,接著再調(diào)用類的構造方法。那么,在本例中,為什么在變量定義時便初始化的代碼反而會出現(xiàn)空指針違例呢?

對象的創(chuàng)建過程和初始化

實際上,前面提到的“變量初始化發(fā)生在調(diào)用任何方法包括構造方法之前”這句話是不確切的,當我們把眼光集中在單個類上時,該說法成立;然而,當把視野擴大到具有繼承關系的兩個或多個類上時,該說法不成立。

對象的創(chuàng)建一般有兩種方式,一種是用new操作符,另一種是在一個Class對象上調(diào)用newInstance方法;其創(chuàng)建和初始化的實際過程是一樣的:

首先為對象分配內(nèi)存空間,包括其所有父類的可見或不可見的變量的空間,并初始化這些變量為默認值,如int類型為0,boolean類型為false,對象類型為null;

然后用下述5個步驟來初始化這個新對象:

1)分配參數(shù)給指定的構造方法;
2)如果這個指定的構造方法的第一個語句是用this指針顯式地調(diào)用本類的其它構造方法,則遞歸執(zhí)行這5個步驟;如果執(zhí)行過程正常則跳到步驟5;
3)如果構造方法的第一個語句沒有顯式調(diào)用本類的其它構造方法,并且本類不是Object類(Object是所有其它類的祖先),則調(diào)用顯式(用super指針)或隱式地指定的父類的構造方法,遞歸執(zhí)行這5個步驟;如果執(zhí)行過程正常則跳到步驟5;
4)按照變量在類內(nèi)的定義順序來初始化本類的變量,如果執(zhí)行過程正常則跳到步驟5;
5)執(zhí)行這個構造方法中余下的語句,如果執(zhí)行過程正常則過程結束。

這一過程可以從下面的時序圖中獲得更清晰的認識:

如何通過實例學習Java對象的構造過程

對分析本文的實例最重要的,用一句話說,就是“父類的構造方法調(diào)用發(fā)生在子類的變量初始化之前”??梢杂孟旅娴睦觼碜C明:

// Petstore.java
class Animal {
Animal() {
System.out.println("Animal");
}
}
class Cat extends Animal {
Cat() {
System.out.println("Cat");
}
}
class Store {
Store() {
System.out.println("Store");
}
}
public class Petstore extends Store{
Cat cat = new Cat();
Petstore() {
System.out.println("Petstore");
}
public static void main(String[] args) {
new Petstore();
}
}
運行這段代碼,它的執(zhí)行結果如下:

Store
Animal
Cat
Petstore
從結果中可以看出,在創(chuàng)建一個Petstore類的實例時,首先調(diào)用了它的父類Store的構造方法;然后試圖創(chuàng)建并初始化變量cat;在創(chuàng)建cat時,首先調(diào)用了Cat類的父類Animal的構造方法;其后才是Cat的構造方法主體,最后才是Petstore類的構造方法的主體。

尋找程序產(chǎn)生例外的原因

現(xiàn)在回到本文開始提到的實例中來,當程序創(chuàng)建一個ChildDlg2的實例時,根據(jù)super(null, “Title”)語句,首先執(zhí)行其父類BaseDlg的構造方法;在BaseDlg的構造方法中調(diào)用了createClientPanel()方法,這個方法是抽象方法并且被子類ChildDlg2實現(xiàn)了,因此,實際調(diào)用的方法是ChildDlg2中的createClientPanel()方法(因為Java里面采用“動態(tài)綁定”來綁定所有非final的方法);createClientPanel()方法使用了ChildDlg2類的實例變量jTextFieldName,而此時ChildDlg2的變量初始化過程尚未進行,jTextFieldName是null值!所以,ChildDlg2的構造過程擲出一個NullPointerException也就不足為奇了。

再來看ChildDlg1,它的jTextFieldName的初始化代碼寫在了createClientPanel()方法內(nèi)部的開始處,這樣它就能保證在使用之前得到正確的初始化,因此這段代碼工作正常。

解決問題的兩種方式

通過上面的分析過程可以看出,要排除故障,最簡單的方法就是要求項目組成員在繼承使用BaseDlg類,實現(xiàn)createClientPanel()方法時,凡方法內(nèi)部要使用的變量必須首先正確初始化,就象ChildDlg1一樣。然而,把類變量放在類方法內(nèi)初始化是一種很不好的設計行為,它最適合的地方就是在變量定義塊和構造方法中。

在本文的實例中,引發(fā)錯誤的實質(zhì)并不在ChildDlg2上,而在其父類BaseDlg上,是它在自己的構造方法中不適當?shù)卣{(diào)用了一個待實現(xiàn)的抽象方法。

從概念上講,構造方法的職責是正確初始化類變量,讓對象進入可用狀態(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)建對話框實例后,必須調(diào)用此方法來布局用戶界面
*/
public void initGUI() {
this.getContentPane().add(createClientPanel(), BorderLayout.CENTER);
}

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

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

private JPanel createButtonPanel {
... // 創(chuàng)建按鈕區(qū)域
}
}
新的BaseDlg類增加了一個initGUI()方法,程序員可以這樣使用這個類:

ChildDlg dlg = new ChildDlg();
dlg.initGUI();
dlg.setVisible(true);
總結

類的構造方法的基本目的是正確初始化類變量,不要賦予它過多的職責。

設計類構造方法的基本規(guī)則是:用盡可能簡單的方法使對象進入就緒狀態(tài);如果可能,避免調(diào)用任何方法。在構造方法內(nèi)唯一能安全調(diào)用的是基類中具有final屬性的方法或者private方法(private方法會被編譯器自動設置final屬性)。final的方法因為不能被子類覆蓋,所以不會產(chǎn)生問題。

關于如何通過實例學習Java對象的構造過程就分享到這里了,希望以上內(nèi)容可以對大家有一定的幫助,可以學到更多知識。如果覺得文章不錯,可以把它分享出去讓更多的人看到。

向AI問一下細節(jié)

免責聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉載和分享為主,文章觀點不代表本網(wǎng)站立場,如果涉及侵權請聯(lián)系站長郵箱:is@yisu.com進行舉報,并提供相關證據(jù),一經(jīng)查實,將立刻刪除涉嫌侵權內(nèi)容。

AI