1. 程式人生 > >Java中如何避免空指標異常

Java中如何避免空指標異常

這個問題對於我來說是一個很常見的問題,這也是由初級程式設計師成長到中級程式設計師的時候經常會遇到的問題。程式設計師不知道或不信任正在使用的約定,並且小心的檢查著null。還有當程式設計師寫程式碼的時候,總是會依賴於通過返回空(NULL)來表明某些意義,因此需要呼叫者去檢查Null。換種方式來說,有兩種空指標的檢查場景:

  1. 期望的結果就是null。
  2. 期望的結果不是null。

第二種很簡單,可以通過用assert或者允許程式報錯,例如丟擲NullPointerException。Assertions是一個從Java1.4加進來的高度未被利用的特性,語法是:

assert <condition
>

或者

assert <condition> : <object>

condition是一個布林表示式,object是一個物件(其toString()方法的輸出將會被包含在錯誤裡)。

校對注:我測試了下,JDK1.4及其以上,執行前設定vm引數-ea

public static void main(String[] args) {

String name = null;

assert (name != null) : &quot;name為空null&quot;;

}
Exception in thread &quot;main&quot;; java.lang.AssertionError: 變數name為空null

at LogUtil.main(LogUtil.java:37)
如果condition為false的話,assert將會丟擲一個Error(AssertionError)。預設Java會忽略斷言你可以通過在JVM中傳入一個-ea引數來啟用斷言。 你可以為單獨的一個包或者類啟動關閉assertions。這意味著你可以在開發和測試的時候通過斷言來驗證程式碼,在釋出產品的時候關閉它,儘管我下面展示的測試中並沒有因為assertions而損失效能。在這個程式碼段中不用斷言也可以,因為他會執行失敗的,就像加了斷言一樣。唯一的區別是有了斷言可能會發生的更快一些,更有意義,並且會附加一些額外的資訊,而這可以幫助你弄明白失敗的原因。 第一種有一點棘手。如果你對不能控制正在呼叫的這段程式碼,那你就卡住了。如果Null是一個合理的返回值,你就應該檢查它。如果是你能夠控制的程式碼,那就是個完全不同的故事情景了。儘量避免用NULL作為返回值。對於返回Collections的集合很容易,返回Empty(一個空集合或者陣列),而不是一直用null作為返回值。對於不是返回Collections的方法會有一點複雜。考慮下面這個例子:
public
interface Action { void doSomething(); } public interface Parser { Action findAction(String userInput); }

Parser採用使用者的輸入作為引數,然後做一些事情(例如模擬一個命令列)。現在你可能會
返回null,如果沒找到對應輸入的動作的話,這就導致了剛才說過的空指標檢查。
一個可選的解決方案是永遠不要返回null,而是返回一個空物件

public class MyParser implements Parser {
  private static Action DO_NOTHING = new Action() {
    public void doSomething() { /* do nothing */ }
  };

  public Action findAction(String userInput) {
    // ...
    if ( /* we can't find any actions */ ) {
      return DO_NOTHING;
    }
  }
}

比較這段程式碼:

Parser parser = ParserFactory.getParser();
if (parser == null) {
  // now what?
  // this would be an example of where null isn't (or shouldn't be) a valid response
}
Action action = parser.findAction(someInput);
if (action == null) {
  // do nothing
} else {
  action.doSomething();
}

和這段:

ParserFactory.getParser().findAction(someInput).doSomething();

這是個更好的設計,因為足夠簡潔,避免了多餘的判斷。即便如此,或許比較合適的設計是:findAction()方法之惡傑丟擲一個異常,其中包含一些有意義的錯誤資訊—–特別是在這個案例中你依賴於使用者的輸入。讓findAction()方法丟擲一個異常而不是簡單的產生一個沒有任何解釋的NullPointerException 要好得多。

try {
    ParserFactory.getParser().findAction(someInput).doSomething();
} catch(ActionNotFoundException anfe) {
    userConsole.err(anfe.getMessage());
}

或者你認為try/catch 的機制太醜了,你的action應該跟使用者提供一個反饋而不是什麼都不做:

public Action findAction(final String userInput) {
    /* Code to return requested Action if found */
    return new Action() {
        public void doSomething() {
            userConsole.err("Action not found: " + userInput);
        }
    }
}