国产探花免费观看_亚洲丰满少妇自慰呻吟_97日韩有码在线_资源在线日韩欧美_一区二区精品毛片,辰东完美世界有声小说,欢乐颂第一季,yy玄幻小说排行榜完本

首頁 > 學院 > 開發(fā)設計 > 正文

懸掛復合錯誤類型

2019-11-18 15:17:36
字體:
供稿:網(wǎng)友

  消滅空指針異常的一個最常見的產(chǎn)生原因

java 編程中,最常見的重復(被抱怨最多的)錯誤之一是空指針異常。跟蹤這些錯誤中的某一個的產(chǎn)生原因,真的會讓您對您當初的擇業(yè)決定產(chǎn)生懷疑。在診斷 Java 代碼的這一部分中,我們通過把和空指針異常聯(lián)系在一起的最常見的一個類型編成目錄,來繼續(xù)我們的錯誤類型檢查,并一步步分析一個含有空指針異常的類的示例。然后我們將回顧幾個編程技巧,幫您減少這種類型錯誤的出現(xiàn)。

空指針到處都有!
在一個 Java 程序員所能碰到的所有異常中,空指針異常屬于最恐怖的,這是因為:它是程序能給出的信息最少的異常。例如,不像一個類轉(zhuǎn)型異常,空指針異常不給出它所需要的內(nèi)容的任何信息,只有一個空指針。此外,它并不指出在代碼的何處這個空指針被賦值。在許多空指針異常中,真正的錯誤出現(xiàn)在變量被賦為空值的地方。為了發(fā)現(xiàn)錯誤,我們必須通過控制流跟蹤,以發(fā)現(xiàn)變量在哪里被賦值,并確定是否這么做是不正確的。當賦值出現(xiàn)在包中,而不是出現(xiàn)在發(fā)生報錯的地方時,進程會被明顯地破壞。

許多 Java 開發(fā)人員告訴我,他們所碰到的絕大多數(shù)程序崩潰是空指針異常,并且他們渴望有一種工具,能在程序第一次運行前靜態(tài)地識別出這些錯誤。不幸的是,自動控制理論告訴我們,沒有工具可以靜態(tài)地決定哪些程序?qū)伋隹罩羔槷惓!5窃谝粋€程序中,用一個工具排除許多空指針異常是有可能的,留給我們僅僅一小部分需要我們必須人工檢查的潛在的問題所在。實際上,為了為 Java 程序(請參閱參考資料)提供這樣一個工具,現(xiàn)在正做著一些研究。但是一個好的工具也只能為我們做這些。空指針異常將決不會被完全根除。當它們真的發(fā)生時,工具能幫我們弄清和它們相聯(lián)系的錯誤類型,這樣我們能快速診斷它們。另外,我們可以應用某些編程和設計技巧來顯著減少這些類型錯誤的出現(xiàn)。

懸掛復合類型
我們將探討的第一個關(guān)于空指針異常的錯誤類型,是一個我稱之為懸掛復合類型的錯誤類型。這種類型的錯誤是這樣產(chǎn)生的:定義的某些基本例沒有被給出它們自己的類,然后以這種方法定義了一個遞歸的數(shù)據(jù)類型。相反,空指針被插入到不同的復合數(shù)據(jù)類型中。數(shù)據(jù)類型實例的使用就似乎空指針被正確填充了一樣。我稱之為懸掛復合類型是因為沖突代碼是復合設計類型的一個有缺點的應用程序,其中,復合數(shù)據(jù)類型包含懸掛的引用(也就是空指針)。

原因
考慮下面 LinkedList 類的單連接執(zhí)行,它有一個懸掛復合類型。為了示例的簡單起見,我只執(zhí)行在 java.util.LinkedList 中定義的一些方法。為了顯示這種類型的錯誤是多么隱蔽,我已經(jīng)在下面代碼中引入一個錯誤。看看你是否能發(fā)現(xiàn)它。

清單 1. 單連接鏈表

import java.util.NoSUChElementException;

public class LinkedList {

PRivate Object first;
private LinkedList rest;

/**
* Constructs an empty LinkedList.
*/
public LinkedList() {
this.first = null;
this.rest = null;
}

/**
* Constructs a LinkedList containing only the given element.
*/
public LinkedList(Object _first) {
this.first = _first;
this.rest = null;
}

/**
* Constructs a LinkedList consisting of the given Object followed by
* all the elements in the given LinkedList.
*/
public LinkedList(Object _first, LinkedList _rest) {
this.first = _first;
this.rest = _rest;
}
}

這段代碼相當?shù)脑愀狻K趦蓚€域中都放置一個空指針來表示空鏈表,而不是為空鏈表定義一個單獨的類。一開始看來,用這種方法表示一個空鏈表使代碼簡單。究竟,我們不必僅僅為了空鏈表而去定義一個額外的類。但是,正如我將證實的,這樣的簡單操作只是一個幻想。讓我們?yōu)檫@個類定義一些讀取器 (getter) 和設置器 (setter) 方法:

清單 2. 為 LinkedList 定義方法

public Object getFirst() {

if (! (this.isEmpty())) {
return this.first;
}
else {
throw new NoSuchElementException();
}
}
public LinkedList getRest() {

if (! (this.isEmpty())) {
return this.rest;
}
else {
throw new NoSuchElementException();
}
}

public void addFirst(Object o) {

LinkedList oldThis = (LinkedList)this.clone();

this.first = o;
this.rest = oldThis;
}

public boolean isEmpty() {
return this.first == null && this.rest == null;
}

private Object clone() {
return new LinkedList(this.first, this.rest);
}

注重,兩個讀取器采取的行動依靠于是否鏈表為空。這正好是那種一個正確構(gòu)建的類層次所要防止的 if-then-else 鏈。由于這些鏈,我們不用在一個單一類型的鏈表上孤立地考慮這些讀取器。此外,假如在將來的某一天,我們需要第三種類型的鏈表(例如一個不可變的鏈表),我們將不得不重新編寫每一個方法的代碼。

但是真正簡單的方法是我們怎樣才能輕易地避免將錯誤引入到程序中。按照這種方法,清單 2 中的 LinkedList 的執(zhí)行只能是可憐的失敗。實際上,就象我前面提到的,我們的 LinkedList 類已經(jīng)包含一個微小的但有破壞性的錯誤(你發(fā)現(xiàn)了嗎?)。空鏈表的表示到底是什么呢?我前面說過,空鏈表就是兩個域都包含一個空指針的 LinkedList。實際上,零參數(shù)構(gòu)造器就是建立一個這樣的空鏈表。但是注重單參數(shù)構(gòu)造器 不是把空鏈表放入到 rest 域,這是構(gòu)建一個只有一個值的鏈表所必須的。相反,它是用空指針替代。由于懸掛復合類型錯誤將空指針和基本例的位置標記符相混淆,象這樣的錯誤是很輕易犯的。為了了解這錯誤怎樣表明自己是一個空指針異常,讓我們?yōu)榍鍐螌懸粋€ equals 方法:

清單 3. 哪里錯了

public boolean equals(Object that) {

// If the objects are not of the same class, then they are not equal.
// Reflection is used in case this method is called from an instance of a
// subclass.
if (this.getClass() == that.getClass()) {

LinkedList _that = (LinkedList)that;

if (this.isEmpty() _that.isEmpty()) {
return this.isEmpty() && _that.isEmpty();
}
else {

boolean firstEltsMatch = this.getFirst().equals(_that.getFirst());
boolean restEltsMatch = this.getRest().equals(_that.getRest());

return firstEltsMatch && restEltsMatch;
}
}
else {
return false;
}
}

假如 this 和 that 都是非空,那么 equals 方法可以正確地預計它能調(diào)用它們的 getFirst 和 getRest 而不出現(xiàn)錯誤信息。但是假如鏈表中的任意一個包含用單參數(shù)構(gòu)造器建立的任何部分,那么,在一個空鏈應該等待的地方,這個遞歸調(diào)用將最終表示為一個空指針。當它調(diào)用 getFirst 或 getRest 時,一個空指針異常就出現(xiàn)了。

一種觀點可能是簡單地直接把空鏈表表示成空指針,但是這個想法完全不可行的,因為在那時,不可能去掉鏈表的最后一個元素和在空鏈表中插入一個元素。

另一方面,可以照下面的方法重寫單參數(shù)構(gòu)造器來修復錯誤:

清單 4. 修復錯誤

public LinkedList(Object _first) {
this.first = _first;
this.rest = new LinkedList();
}

但是,象大多數(shù)的錯誤類型一樣,阻止它們的出現(xiàn)總比修補它們要好的多。修補錯誤使得代碼很輕易被打斷,即使簡單的讀取器、設置器和 equals 方法都會變得龐大,這樣一個事實建議我們要采取一種更好的設計方法。

解決方法和預防措施
事實上有一個簡單的辦法來避免懸掛復合錯誤:給每個數(shù)據(jù)類型的基本例定義一個自己的類。我建議執(zhí)行有著 LinkedList 類的鏈表,該類包含一個有 Empty 類或 Cons 類的域,而不是象我們前面做的那樣,單個執(zhí)行鏈接鏈表。這些類執(zhí)行一個公共接口,如圖 1 所示。

圖 1. Empty 和 Cons UML 示意圖

為了執(zhí)行可變的方法,新的 LinkedList 類作為一個內(nèi)部不可變的鏈表的容器,如清單 5 所示。這個步驟是必須的,因為真正的空鏈表沒有域可變,所以它們是不可變的。

清單 5. 每個基本例獲得自己的類

import java.util.NoSuchElementException;

public class LinkedList {

private List value;

/**
* Constructs an empty LinkedList.
*/
public LinkedList() { this.value = new Empty(); }

/**
* Constructs a LinkedList containing only the given element.
*/
public LinkedList(Object _first) { this.value = new Cons(_first); }

/**
* Constructs a LinkedList consisting of the given Object followed by
* all the elements in the given LinkedList.
*/
public LinkedList(Object _first, LinkedList _rest) {
this.value = new Cons(_first, _rest.value);
}

private LinkedList(List _value) { this.value = _value; }

public Object getFirst() { return this.value.getFirst(); }
public LinkedList getRest() { return new LinkedList(this.value.getRest()); }
public void addFirst(Object o) { this.value = new Cons(o, this.value); }
public boolean isEmpty() { return this.value instanceof Empty; }

public boolean equals(Object that) {

if (this.getClass() == that.getClass()) {

// The above test guarantees that the cast to LinkedList will always
// succeed.
return this.value.equals(((LinkedList)that).value);
}
else {
return false;
}
}
}

那時,執(zhí)行一個不可變的鏈表是直截了當?shù)模缜鍐?6 所示。

清單 6. 對節(jié)點作加法和乘法的方法

interface List {
public Object getFirst();
public List getRest();
}

class Empty implements List {
public Object getFirst() { throw new NoSuchElementException(); }
public List getRest() { throw new NoSuchElementException(); }
public boolean equals(Object that) {
return this.getClass() == that.getClass(); }
}

class Cons implements List {

Object first;
List rest;

Cons(Object _first) {
this.first = _first;
this.rest = new Empty();
}
Cons(Object _first, List _rest) {
this.first = _first;
this.rest = _rest;
}
public Object getFirst() { return this.first; }
public List getRest() { return this.rest; }

public boolean equals(Object that) {
if (this.getClass() == that.getClass()) {

// The above test guarantees that the cast to Cons will always succeed.
Cons _that = (Cons)that;

boolean firstEltsMatch = this.getFirst().equals(_that.getFirst());
boolean restEltsMatch = this.getRest().equals(_that.getRest());
return firstEltsMatch && restEltsMatch;
}
else {
return false;
}
}
}

每一個方法的邏輯現(xiàn)在相當簡單了。也請注重,雖然就如以前在單參數(shù) Cons 構(gòu)造器中一樣,它仍然可能引入同樣的錯誤,但我們已經(jīng)構(gòu)造了一個顯式 Empty 類的事實使這種可能性大大減少。另外,任何阻斷我們的鏈表以及忽略檢查空例的代碼將返回一個 NoSuchElementException,而不是那些沒什么用的空指針異常。

這段代碼的一個簡單優(yōu)化是對 Empty 類應用同一個設計類型,因為每一個 Empty 的實例都是同樣的。我省去了這個優(yōu)化,因為它不能相應地消除空指針異常,并且使得代碼更復雜了一些。

總結(jié)
下面是這個星期的錯誤類型的分析:

類型:懸掛復合

癥狀:使用遞規(guī)定義的數(shù)據(jù)類型的代碼報告一個空指針異常。

原因:定義的某些基本例沒有給出自己的類,然后以這種方法定義了遞歸數(shù)據(jù)類型。相反,空指針被插入到不同的復合數(shù)據(jù)類型。客戶端代碼對基本例處理不一致。

解決方法和預防措施:確保基本例的表示和檢查的一致性。為每個基本例給出一個自己的類。

我們這時還不能結(jié)束對空指針問題的討論。在下一個部分,我們將還要注視另外一個非常普遍的,也被證實是一個空指針異常的錯誤類型,以及怎樣識別它和避免它。

參考資料

靜態(tài)確定可能出現(xiàn)的空指針異常的方法是一種稱為基層設定分析的技術(shù)。Carnegie Mellon 學院的計算機科學網(wǎng)站提供了一種關(guān)于這種方法的簡短介紹,同時還有幾個相關(guān)主題的技術(shù)出版物的鏈接。
Depaul 大學的 Division of Software Engineering 已經(jīng)在自動化定理方面做了一些工作,在 Java 代碼中偵測出空指針異常。
請訪問 Patterns 主頁獲取一個關(guān)于設計類型和怎樣使用它們的介紹。
請查看 JUnit,通過使您的代碼 "test-infested" 來捕捉更多的錯誤。
請一定要閱讀一下 Eric Allen 關(guān)于 錯誤類型的第一篇文章(developerWorks,2001 年 2 月)。
對一般的調(diào)試感愛好嗎?請查看這個免費的、dW 專有的教程、 Java 調(diào)試,來學習更多的從理論到應用的調(diào)試技巧。
關(guān)于作者
Eric Allen 畢業(yè)于 Cornell 大學,獲得計算機科學和數(shù)學的學士學位。他目前是 Cycorp,Inc. Java 軟件開發(fā)的負責人,并是 Rice 大學的編程語言小組的一個兼職研究生。他的研究涉及在源程序和字節(jié)碼層次方面的正式語義模型和 Java 語言擴展的開發(fā)。目前,他正在為 NextGen 編程語言(類屬運行類型的 Java 語言擴展)實現(xiàn)一個源程序到字節(jié)碼的編譯器。可通過 eallen@cyc.com 聯(lián)系 Eric。

發(fā)表評論 共有條評論
用戶名: 密碼:
驗證碼: 匿名發(fā)表
主站蜘蛛池模板: 会同县| 乌鲁木齐县| 云梦县| 通江县| 大洼县| 辽中县| 精河县| 象州县| 乌拉特后旗| 南川市| 灵武市| 通辽市| 乌审旗| 百色市| 钟祥市| 社旗县| 金溪县| 河北省| 曲松县| 金川县| 绵竹市| 客服| 闻喜县| 合作市| 南乐县| 丰宁| 乾安县| 上高县| 敖汉旗| 阜城县| 宝山区| 如皋市| 都昌县| 嘉黎县| 姜堰市| 雷波县| 银川市| 长子县| 普陀区| 三河市| 营口市|