2010-07-11 101 views
8

我想知道使用try/exception來處理空值與使用if語句首先檢查空值相比的成本。Java:檢查null或允許異常處理

提供更多信息。獲得空值的可能性> 50%,因爲在此應用中。如果沒有輸入數據,那麼通常會出現null ...所以嘗試使用空值進行計算是司空見慣。

這就是說,它會提高性能,如果我使用if語句在計算之前先檢查null,而不是首先嚐試計算,或者只是拋出異常並處理它?

感謝您的任何建議:-)

感謝偉大的發人深省的反饋!下面是一個僞代碼例子來闡明原題:

BigDecimal value1 = null //assume value1 came from DB as null 
BigDecimal divisor = new BigDecimal("2.0"); 

try{ 
    if(value1 != null){ //does this enhance performance?... >50% chance that value1 WILL be null 
     value1.divide(divisor); 
    } 
} 
catch (Exception e){ 
    //process, log etc. the exception 
    //do this EVERYTIME I get null... or use an if statement 
    //to capture other exceptions. 
} 
+2

ack ...不要捕捉異常...如果除數爲零,該怎麼辦?然後,您還將捕獲除以零的ArithmeticException,但將其視爲「預期的」NullPointerException。儘可能具體說明你想要捕捉的異常情況。 – TofuBeer 2010-07-11 21:27:45

+0

有沒有人想把自己的脖子伸出來,並說if語句比拋出異常要便宜? – AJay 2010-07-12 00:50:32

+0

是的,我喜歡。 'if'語句比拋出異常要便宜得多。 – EJP 2010-07-12 06:37:05

回答

11

我建議你檢查空,而不是做計算,而不是拋出異常。

一個例外應該是「特殊」且罕見的,而不是管理控制流的方式。

我還建議您與客戶就輸入參數建立合同。如果允許空位拼出來,如果它們不是,請清楚應該傳遞什麼,默認值,以及如果傳遞空值時您承諾返回的內容。

+0

絕對是真的 – odiszapc 2013-09-18 10:22:57

3

如果有> 50%的機會得到一個null,那麼它幾乎不是一個例外?

就個人而言,我會說,如果你希望一些事情發生,你應該爲它適當的代碼 - 在這種情況下,檢查空和做什麼是適當的。我總是理解拋出一個例外,不是非常便宜,但不能肯定地說。

7

如果通過null參數爲例外情況,那麼我會拋出一個NullPointerException

public Result calculate(Input input) { 
    if (input == null) throw new NullPointerException("input"); 
    // ... 
} 

如果通過null允許的情況下,那麼我會跳過計算,最終又返回null。但這在我看來意義不大。首先傳遞null似乎是調用代碼中的一個錯誤。

無論您選擇什麼方式,都應該在Javadoc中正確記錄以避免API用戶感到意外。

+1

在這種情況下,如果您沒有記錄「輸入」被提供爲空是可以接受的,那麼我會拋出'IllegalArgumentException'而不是'NullPointerException'。我將'NullPointerException'解釋爲類似於分段錯誤:主機運行時檢測爲編程錯誤,而不是應用程序本身使用的東西。 根據我自己的建議,JDK文檔讀取:「應用程序應該拋出此類的實例以指示其他非法使用null對象。」 http://java.sun.com/javase/7/docs/api/java/lang/NullPointerException.html – seh 2010-07-11 16:51:18

+1

@seh:_Effective Java 2nd Edition_聲明'NullPointerException'是非法'null'參數的約定。 – polygenelubricants 2010-07-11 17:02:03

+2

@seh:'IllegalArgumentException'只適用於不爲'null'的非法參數。編輯:@poly說,是的:)在Java SE/EE API中窺視,你會發現這個約定也適用於那裏。 – BalusC 2010-07-11 17:03:06

3

嘗試和趕上接近「免費」,但投擲可能非常昂貴。通常,VM創建者不會優化異常路徑,因爲它們是例外的(應該很少見)。

NullPointerException指示程序員錯誤(RuntimeException)並且不應被捕獲。而不是捕獲NullPointerException,你應該修復你的代碼,以使異常不被拋出。

更糟的是,如果您發現NullPointerException,並且計算代碼的不同部分會拋出NullPointerException,那麼您現在已經屏蔽了一個錯誤。

爲了完全回答你的問題,我會實現它的一種方式,配置文件,然後以另一種方式實現它,然後配置文件...然後我只會使用帶有if語句的函數來避免拋出NullPointerException,而不管因爲上面的觀點,速度更快。

0

我同意大多數其他答案,您應該更喜歡try-catch的空檢查。我已經提高了其中的一些。

但是你應該儘量避免這種需要。

獲得空值的概率大於50%,因爲在這個應用程序中。如果沒有輸入數據,那麼通常會出現null ...所以嘗試使用空值進行計算是司空見慣。

這就是你應該真正解決的問題。

提出合理的默認值以確保計算工作正常,或避免在未提供所需輸入數據的情況下調用計算。

對於涉及它們的許多標準數據類型和計算,都有明智的默認值。默認數字爲0或1,具體取決於它們的含義,缺省字符串和集合爲空,許多計算正常。對於您自己製作的更復雜的物體,請考慮Null Object模式。

+0

默認值本身最終可能是隱藏錯誤的情況。這不是沒有限制,但默認情況下,你仍然更好地拋出異常。回退需要很少應用,並且要根據具體情況應用,只有在真正有意義且理想的用戶清楚的地方,例如yes/no,default yes :)。默認值應該比糾正錯誤更方便。 – jgmjgm 2018-02-28 15:55:05

0

如果您有任何結果或輸入無法由您的程序處理的情況下,那應該是一個錯誤。你應該知道你的程序可以處理和只允許。對於未來可能出現的情況,如果結果可以處理,但還沒有結果,我仍然建議將其視爲一個錯誤,直到您真正需要結果爲止。如果有疑問,你不知道,程序不知道,它不能處理,你沒有配備你的程序來處理它,所以這是一個錯誤。該計劃不能做任何事情,只能停下來。

對於用戶輸入來說,依靠程序最終崩潰是一個非常糟糕的主意。你不知道什麼時候,甚至會不會崩潰。它可能只是最終做錯了事,或者做了十件事,然後崩潰,它不應該做,並且如果輸入已被驗證,則不會做。

在防範自己的錯誤方面,更多的是混合包。您需要更多地關注通過測試來確保工作正常,消除未知因素,校對閱讀,確保您確切知道程序的工作方式等。您偶爾還會遇到內部處理可能產生不良結果的情況。

事實證明,對於給定的情況,結果不是錯誤,你不處理異常,你處理null,而不是一個例外。在這種情況下,結果不是錯誤。所以你處理結果而不是錯誤。這不可能更簡單。如果你正在捕捉一個異常,然後做一些可以做的事情,如果這樣做,如:

try 
    extra = i_need_it(extra_id) 
    show('extra', extra) 
catch 
    show('add_extra') 

然後,這是不對的。如果你沒有額外的東西,你有一個完全可以接受的行動方案。

這是好多了,它讓你的意圖明顯沒有額外的冗長:這裏

Something extra = i_want_it(extra_id) 
if extra ==== null 
    show('add_extra') 
else 
    show('extra', extra) 

通知你需要什麼特別的避免受涼從另一層異常。我如何把上面的嘗試抓住是一個不好的做法。你應該只是包裝引發異常的東西:

Something extra 
try 
    extra = i_need_it(extra_id) 
if extra === null 
    show('add_extra') 
else 
    show('extra', extra) 

當你這樣的事情,那麼它只是將null轉換爲異常,然後再回來。這是Yo-Yo編碼。直到你實際上是能夠實現處理的空

Object i_need_it(int id) throws 

你應該開始。如果您能夠實現對異常的處理,則可以實現對null的處理。

當事實證明,事情並不總是需要添加任何此方法或更改i_need_it它(如果爲null總是處理):

Object|null i_want_it(int id) 

另一種方法是檢查是首先存在:

bool does_it_exist(int id) 

不這樣做,所以經常的原因是因爲它通常出來是這樣的:

if(does_it_exist(id)) 
    Something i_need = i_need_it(id) 

這往往更容易出現併發問題,可能需要更多可能不可靠的調用(網絡上的IO),並且可能效率低下(兩個RTT而非一個)。其他調用通常會像這樣合併,如更新(如果存在),插入(如果唯一),更新(如果存在或插入)等,然後返回通常是最初檢查的結果。其中一些在有效載荷大小效率和RTT效率方面存在衝突,其也可以根據多個因素而變化。

然而,當需要根據存在與否存在交替行爲時更便宜,但是您不需要處理它。如果你也不需要擔心上述問題,它會更清晰一些。

你可能甚至想:

void it_must_exist(int id) throws 

這又是有用的,因爲如果你只需要確保的東西存在,它往往比得到更便宜。然而,很少有人會需要這樣做,因爲在大多數情況下,您會想知道是否存在某些內容以便直接處理它。

構想它的一種方式是,如果'does_it_exist'只是簡單地在調用堆棧上顯式返回一個布爾值,那麼'i_want_it'是組合'has'和'get'的效果。

雖然有兩種不同的方法更加明確分離方法簽名,有時你可能需要從別的東西和最簡單的方式傳遞下來,如果你不是我的位模糊的是:

Object|null get(int id, bool require) throws 

這是更好的,因爲你將合同交給呼叫鏈,而不是建立在遠離行動的沙灘上。有許多方法可以更明確地傳遞你的契約,但它往往會讓人費解YAGNI(IE,傳遞一個方法調用者)。

你應該提前拋出異常,你可以想要安全而不是遺憾,所以誤報是好的。一旦你發現它是一個誤報,儘管如此,你可以在源頭修復它。

你應該極其罕見地處理異常。絕大多數應該擊中頂部,然後調用日誌記錄和輸出處理程序。通過直接傳遞結果並處理它,您可以適當地修復其他問題。當你有許多用途中有一個誤報時,只有這個用法。不要只是刪除根目錄中的檢查,並打破許多其他仍然存在錯誤的情況。

Java是一種不幸的語言,因爲我不能有一種說法不傳遞null的方法,或者這個變量必須是非空的。

當缺少這樣的功能時,通常最好在它們的來源(例如IO)中檢查空值,而不是每次將某個東西傳遞給某個方法時使用。否則,這是一個荒謬的空值檢查。

如果你真的需要這個,你可以應用這個模式來創建函數來替換你的參數檢查ifs。您將與對象替換ID本身如:

Object i_want(Object it) throws 
    if(it === null) 
     throw 
    return it; 

然後:

void give_me(Object it) 
    this.it = Lib<Object>::i_want(it) 

一種恥辱沒有中繼類型。

或者:

void i_get_it(Getter<Object> g) 
    this.it = Lib<Object>::i_want(g.gimme()) 

你可能有一個特定的吸氣劑,而不是通用的。

或者:

void i_need_it(Result<Object> r) 
    this.it = r.require() 

如果你只能做它的邊界而不是在(當你打電話東西拿出來控制,其中非空的結果不能得到保證的一側),而最好是做它在那裏或任何用法的方法被記錄爲返回null並且只在那裏,因爲這是真正需要的地方,這就意味着當你得到一個null不屬於它的地方時(錯誤發生),你不會去有一個容易的時間找出它來自哪裏。它可以從IO傳來,並且不會產生空指針異常,除非有東西試圖對其進行操作。這些空值可以在系統中傳遞幾天甚至幾個月,作爲等待中止的定時炸彈。

我不會自己這樣做,但在某些情況下,我不會責怪人們實施上面的防禦性編程方法,因爲Java的破壞類型系統默認情況下是鬆散的並且不能被限制。在強類型語言中,除非明確聲明,否則不允許使用null。

請注意,儘管我稱之爲破壞,但幾十年來我一直在使用明顯較寬鬆的語言來構建大型,複雜和關鍵系統,而不必花費過多的檢查來代碼。紀律和能力決定了質量。

假結果或情況發生時,您認爲是所有調用者都無法處理的結果,但至少有一個調用者可以正確處理它。在這種情況下,你不會處理異常,而是給調用者提供結果。這很少有例外。

Java 8具有可選功能,但看起來並沒有什麼幫助。這是內部平臺效應的一個可怕的例子,它試圖將新的語法作爲類實現,並且最終不得不添加一半現有的Java語法,使得它非常笨重。像往常一樣,現代Java OOP通過增加更多功能和更復雜的東西解決了每個人都希望減少欺騙的問題。如果你真的想像那樣鏈接,你可能想嘗試一些例如直接實現該語法的kotlin。

現代語言將意味着你不必擔心極本,只是有:

void i_need_it(Object it) 
    this.it = it 

void i_want_it(Object? it) 
    this.it = it 

現代語言甚至可能回到原來的對象的方法,而不返回(替換爲空自我作爲標準和自動返回),所以人們可以在編程中擁有自己的花哨鏈接或其他任何時髦的東西。

你不能有一個基類的工廠給你一個Null或NotNull,因爲你仍然需要傳遞基類,並且當你說你想要NotNull的時候這將是一個類型違規。

你可能想玩弄方面,靜態分析等,雖然這有點兔子漏洞。所有的文檔都應該指出是否可以返回null(儘管如果是間接的,它可能會被忽略)。

你可以讓一個類如MightHave包裹你的結果,你可以把它放在get,require等方法上,如果你不喜歡靜態但是想吃一個if雖然它也處於輕度複雜的領域並將所有方法簽名搞亂,將遍佈各處的所有東西都捆綁起來,這是一個醜陋的解決方案。作爲替代罕見異常處理的例子,由於調用圖的複雜性和各層中未知數的數量,異常看起來很有用,它只是非常方便。

一個極其罕見的情況是,當你的源代碼知道要拋出什麼異常時,如果它需要拋出但不能傳遞下去(儘管在兩個層之間需要接近警告)。有些人可能也想要這樣做,因爲它可以輕鬆地追蹤丟失項目來自哪裏以及需要哪些項目,這些都是使用檢查可能無法提供的(它們很可能在靠近源的位置失敗,但不能保證)。應該謹慎,因爲這些問題可能比合理多態性,元/動態編碼等問題更能表明流控制不佳或過於複雜。

應該注意事項,如默認值或空對象模式。兩者都可能最終隱藏錯誤,成爲最好的猜測或治癒疾病。通常可以使用NullObject模式和Optional兩種方法來簡單地關閉或雙向傳遞解釋器中的內置錯誤處理。

默認並不總是不好,但可以落入猜測的領域。隱藏來自用戶的錯誤最終將其設置爲失敗。默認(和sanitisation)總是需要仔細考慮。

諸如NullObject模式和Optional之類的東西可以被過度使用以有效地關閉空指針檢查。它只是假設null不會是一個錯誤,它最終會導致程序在做某些事情,而不是其他事情,但你不知道是什麼。在某些情況下,這可能會產生令人捧腹的結果,例如用戶的信用卡爲空。確保你正在使用這些東西,而不是將它們用於將所有代碼簡單地包裝在忽略空指針異常的try catch中的效果。這是非常普遍的,因爲人們傾向於修復引發異常的錯誤。事實上,真正的錯誤往往會更遠。最終會出現一個錯誤的IO處理錯誤返回null,並且null在程序中傳遞。與其修復null的一個來源,人們會嘗試修復它到達的所有地方,從而導致錯誤。

NullObject模式或MagicNoop模式有它們的地方,但不是常用的。你不應該使用它們,直到它變得顯而易見,它們以合理的方式是有用的,不會導致比解決問題更多的問題。有時一個noop實際上是一個錯誤。