java - nullpointerexception意思 - nullpointerexception解決




避免!= null語句 (20)

如果不允許使用未定義的值:

您可以配置IDE以警告您可能存在空取消引用。 例如,在Eclipse中,請參閱首選項> Java>編譯器>錯誤/警告/空分析

如果允許未定義的值:

如果要定義未定義值有意義的新API ,請使用選項模式 (可能熟悉函數式語言)。 它具有以下優點:

  • API中明確說明輸入或輸出是否存在。
  • 編譯器強制您處理“未定義”的情況。
  • Option是monad ,因此不需要詳細的null檢查,只需使用map / foreach / getOrElse或類似的組合器來安全地使用該值(example)

Java 8有一個內置的Optional類(推薦); 對於早期版本,有庫替代品,例如GuavaOptionalFunctionalJavaOption 。 但是與許多功能樣式模式一樣,在Java中使用Option(甚至8)會產生相當多的樣板,使用較簡潔的JVM語言(例如Scala或Xtend)可以減少這種樣板。

如果你必須處理可能返回null的API ,那麼你在Java中做不了多少。 Xtend和Groovy有Elvis運算符 ?:null-safe dereference運算符 ?. ,但請注意,如果是空引用,則返回null,因此它只是“延遲”對null的正確處理。

我使用object != null來避免NullPointerException

有沒有一個很好的替代品呢?

例如:

if (someobject != null) {
    someobject.doCalc();
}

當不知道對像是否為null時,這可以避免NullPointerException

請注意,接受的答案可能已過期,請參閱https://stackoverflow.com/a/2386013/12943以獲取更新的方法。


如果不允許空值

如果您的方法是外部調用的,請從以下內容開始:

public void method(Object object) {
  if (object == null) {
    throw new IllegalArgumentException("...");
  }

然後,在該方法的其餘部分中,您將知道該對object不為null。

如果它是一個內部方法(不是API的一部分),只需記錄它不能為null,就是這樣。

例:

public String getFirst3Chars(String text) {
  return text.subString(0, 3);
}

但是,如果您的方法只是傳遞了值,並且下一個方法將其傳遞給它,則可能會出現問題。 在這種情況下,您可能需要檢查上面的參數。

如果允許null

這真的取決於。 如果發現我經常做這樣的事情:

if (object == null) {
  // something
} else {
  // something else
}

所以我分支,做兩件完全不同的事情。 沒有醜陋的代碼片段,因為我真的需要根據數據做兩件事。 例如,我應該處理輸入,還是應該計算一個好的默認值?

我很少使用成語“ if (object != null && ... ”)。

如果您顯示通常使用成語的示例,則可能更容易為您提供示例。


Google集合框架提供了一種實現空檢查的優雅方式。

像這樣的庫類中有一個方法:

static <T> T checkNotNull(T e) {
   if (e == null) {
      throw new NullPointerException();
   }
   return e;
}

用法是(使用import static ):

...
void foo(int a, Person p) {
   if (checkNotNull(p).getAge() > a) {
      ...
   }
   else {
      ...
   }
}
...

或者在你的例子中:

checkNotNull(someobject).doCalc();

Java中常見的“問題”確實存在。

首先,我對此的看法:

我認為在傳遞NULL時“吃”某些東西是不好的,其中NULL不是有效值。如果你沒有以某種錯誤退出該方法,那麼這意味著你的方法沒有出錯,這是不正確的。然後你可能在這種情況下返回null,並且在接收方法中你再次檢查null,它永遠不會結束,你最終得到“if!= null”等。

因此,IMHO,null必須是一個嚴重的錯誤,它會阻止進一步執行(即null不是有效值)。

我解決這個問題的方法是這樣的:

首先,我遵循這個慣例:

  1. 所有公共方法/ API總是檢查其參數是否為null
  2. 所有私有方法都不檢查null,因為它們是受控方法(如果上面沒有處理的話,只需使用nullpointer異常)
  3. 唯一不檢查null的其他方法是實用程序方法。它們是公開的,但如果你出於某種原因打電話給它們,你就知道你傳遞了什麼參數。這就像試圖在沒有提供水的情況下在水壺中煮水...

最後,在代碼中,public方法的第一行如下:

ValidationUtils.getNullValidator().addParam(plans, "plans").addParam(persons, "persons").validate();

請注意,addParam()返回self,因此您可以添加更多參數進行檢查。

如果任何參數為null,validate()則拋出方法將被檢查ValidationException(選中或取消選中更多是設計/品味問題,但我ValidationException的選中)。

void validate() throws ValidationException;

例如,如果“plans”為null,則該消息將包含以下文本:

參數[plans]遇到非法參數值null

正如您所看到的,用戶消息需要addParam()方法(字符串)中的第二個值,因為您無法輕鬆檢測傳入的變量名稱,即使使用反射(無論如何都不是此帖子的主題......)。

是的,我們知道超出這一行我們將不再遇到空值,所以我們只是安全地調用這些對像上的方法。

這樣,代碼就乾淨,易於維護和讀取。


僅限於這種情況 -

在調用equals方法之前不檢查變量是否為null(下面的字符串比較示例):

if ( foo.equals("bar") ) {
 // ...
}

如果foo不存在,將導致NullPointerException

如果你像這樣比較你的String ,你可以避免這種情況:

if ( "bar".equals(foo) ) {
 // ...
}

哇,當我們有57種不同的推薦NullObject pattern方法時,我幾乎NullObject pattern添加另一個答案,但我認為有些人對這個問題感興趣可能想知道桌面上有一個提議,要求Java 7添加“null” -safe handling“ - if-not-equal-null邏輯的簡化語法。

Alex Miller給出的例子如下:

public String getPostcode(Person person) {  
  return person?.getAddress()?.getPostcode();  
}  

?. 表示僅在左標識符不為空時才解除引用,否則將表達式的其餘部分計算為null 。 有些人,比如Java Posse成員Dick Wall和Devoxx選民都非常喜歡這個提議,但也存在反對意見,理由是它實際上會鼓勵更多地使用null作為哨兵價值。

更新:已在Project Coin下提交了Java 7中關於零安全運算符的proposed 語法與上面的示例略有不同,但它是相同的概念。

更新:零安全運營商提案未進入項目硬幣。 因此,您將不會在Java 7中看到此語法。


我可以更一般地回答它!

當方法以我們不期望的方式獲取參數時,我們通常會遇到這個問題(錯誤的方法調用是程序員的錯誤)。例如:您希望獲得一個對象,而不是一個null。你希望得到一個至少有一個字符的字符串,而不是你得到一個空字符串......

所以兩者之間沒有區別:

if(object == null){
   //you called my method badly!

}

要么

if(str.length() == 0){
   //you called my method badly again!
}

在我們執行任何其他功能之前,他們都希望確保我們收到有效參數。

正如其他一些答案中所提到的,為了避免上述問題,您可以按照合同模式設計。請參閱http://en.wikipedia.org/wiki/Design_by_contract

要在java中實現此模式,您可以使用核心java註釋(如javax.annotation.NotNull)或使用更複雜的庫(如Hibernate Validator)

只是一個樣本:

getCustomerAccounts(@NotEmpty String customerId,@Size(min = 1) String accountType)

現在,您可以安全地開發方法的核心功能,而無需檢查輸入參數,它們可以保護您的方法免受意外參數的影響。

您可以更進一步,確保只能在您的應用程序中創建有效的pojos。(來自hibernate驗證器站點的示例)

public class Car {

   @NotNull
   private String manufacturer;

   @NotNull
   @Size(min = 2, max = 14)
   private String licensePlate;

   @Min(2)
   private int seatCount;

   // ...
}

我喜歡Nat Pryce的文章。 以下是鏈接:

在文章中還有一個指向Java Maybe Type的Git存儲庫的鏈接,我發現它很有趣,但我不認為它可以減少檢查代碼的膨脹。在對互聯網進行一些研究之後,我認為!=空代碼膨脹主要是通過精心設計來減少的。


最終,完全解決此問題的唯一方法是使用不同的編程語言:

  • 在Objective-C中,你可以相當於調用一個方法nil,絕對不會發生任何事情。這使得大多數空檢查變得不必要,但它可以使錯誤更難以診斷。
  • Nice是一種Java派生語言,所有類型都有兩個版本:潛在的null版本和非null的版本。您只能在非null類型上調用方法。通過顯式檢查null,可以將可能為空的類型轉換為非null類型。這使得更容易知道哪些空檢查是必要的,哪些不是。

有時,您有一些方法可以對其參數進行操作,從而定義對稱操作:

a.f(b); <-> b.f(a);

如果你知道b永遠不能為空,你可以交換它。 它對equals最有用:而不是foo.equals("bar"); 更好地做"bar".equals(foo);


而不是Null對像模式 - 它有其用途 - 你可能會考慮null對像是一個bug的情況。

拋出異常時,檢查堆棧跟踪並完成錯誤。


這對我來說聽起來像是一個相當普遍的問題,初級到中級開發人員往往會在某些方面面臨這樣的問題:他們要么不知道,要么不信任他們參與的合同,並且防禦性地過度檢查空值。 另外,在編寫自己的代碼時,它們傾向於依賴返回空值來指示某些東西,從而要求調用者檢查空值。

換句話說,有兩個實例進行空檢查:

  1. 如果null是合同方面的有效回复; 和

  2. 哪裡不是有效的回复。

(2)很容易。 使用assert語句(斷言)或允許失敗(例如, NullPointerException )。 斷言是1.4中添加的高度未充分利用的Java功能。 語法是:

assert <condition>

要么

assert <condition> : <object>

其中<condition>是一個布爾表達式, <object>是一個對象,其toString()方法的輸出將包含在錯誤中。

如果條件不為真,則assert語句拋出ErrorAssertionError )。 默認情況下,Java會忽略斷言。 您可以通過將選項-ea傳遞給JVM來啟用斷言。 您可以為各個類和包啟用和禁用斷言。 這意味著您可以在開發和測試時使用斷言驗證代碼,並在生產環境中禁用它們,儘管我的測試表明,斷言沒有性能影響。

在這種情況下不使用斷言是可以的,因為代碼只會失敗,如果使用斷言將會發生這種情況。 唯一的區別是,斷言可能會更快地發生,以更有意義的方式發生,並且可能還有額外的信息,這可能會幫助您弄清楚如果您不期望它發生的原因。

(1)有點難。 如果您無法控制您正在調用的代碼,那麼您就會陷入困境。 如果null是有效響應,則必須檢查它。

如果它是你控制的代碼(然而通常就是這種情況),那麼這是一個不同的故事。 避免使用空值作為響應。 使用返回集合的方法,很容易:幾乎一直返回空集合(或數組)而不是null。

對於非收藏,它可能更難。 以此為例:如果您有這些接口:

public interface Action {
  void doSomething();
}

public interface Parser {
  Action findAction(String userInput);
}

Parser採用原始用戶輸入並找到要做的事情,也許是在為某些事情實現命令行界面時。 現在,如果沒有適當的操作,您可以使合同返回null。 這導致你正在談論的空檢查。

另一種解決方案是永遠不會返回null,而是使用Null Object模式

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方法來說,拋出一個Exception比使用一個簡單的NullPointerException而沒有解釋的調用方法要好得多。

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

或者,如果您認為try / catch機制太難看,而不是Do Nothing,則默認操作應該向用戶提供反饋。

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);
        }
    }
}

Guava是Google非常有用的核心庫,它有一個很好的有用API來避免空值。我發現Guava非常有幫助。

如維基中所述:

Optional<T>是一種用非空值替換可空T引用的方法。Optional可以包含非空T引用(在這種情況下我們說引用是“存在”),或者它可以不包含任何東西(在這種情況下我們說引用是“不存在”)。它永遠不會被稱為“包含空”。

用法:

Optional<Integer> possible = Optional.of(5);
possible.isPresent(); // returns true
possible.get(); // returns 5

Java 7有一個新的java.util.Objects實用程序類,其中有一個requireNonNull()方法。所有這一切都是拋出一個NullPointerException如果它的參數為null,但它會稍微清理一下代碼。例:

Objects.requireNonNull(someObject);
someObject.doCalc();

該方法對於在構造函數中的賦值之前進行checking非常有用,其中每次使用它都可以保存三行代碼:

Parent(Child child) {
   if (child == null) {
      throw new NullPointerException("child");
   }
   this.child = child;
}

Parent(Child child) {
   this.child = Objects.requireNonNull(child, "child");
}

Null不是'問題'。它是complete建模工具集的組成部分。軟件旨在模擬世界的複雜性,而null則承擔其負擔。Null表示 Java等中的“無數據”或“未知”。因此,為這些目的使用null是合適的。我不喜歡'Null對象'模式;我認為它會提升誰來守護守護者 ”的問題。
如果你問我女朋友的名字我會告訴你我沒有女朋友。在Java語言中,我將返回null。另一種方法是拋出有意義的例外來表明一些不可能的問題(或者不要我希望在那裡解決並將其委託給堆棧中較高的位置,以重試或向用戶報告數據訪問錯誤。

  1. 對於“未知問題”,請給出“未知答案”。(從業務角度來看,這是正確的,這是正確的)在使用之前在方法內檢查一次null參數會減少多個調用者在調用之前檢查它們。

    public Photo getPhotoOfThePerson(Person person) {
        if (person == null)
            return null;
        // Grabbing some resources or intensive calculation
        // using person object anyhow.
    }
    

    上一步導致正常的邏輯流程,從我的照片庫中得不到任何不存在的女朋友的照片。

    getPhotoOfThePerson(me.getGirlfriend())
    

    它適合新的Java API(期待)

    getPhotoByName(me.getGirlfriend()?.getName())
    

    雖然對於某些人來說,不是為了找到存儲在數據庫中的照片而是“正常的業務流程”,但我過去常常使用如下所示的對

    public static MyEnum parseMyEnum(String value); // throws IllegalArgumentException
    public static MyEnum parseMyEnumOrNull(String value);
    

    並且不要厭惡鍵入<alt> + <shift> + <j>(在Eclipse中生成javadoc)並為您的公共API編寫三個額外的單詞。對於那些沒有閱讀文檔的人來說,這將是綽綽有餘的。

    /**
     * @return photo or null
     */
    

    要么

    /**
     * @return photo, never null
     */
    
  2. 這是理論上的情況,在大多數情況下,您應該更喜歡java null safe API(如果它將在另外10年內發布),但它NullPointerException是一個的子類Exception因此,它Throwable表示合理的應用程序可能想要捕獲的條件(javadoc)!為了使用異常的第一個最大優勢和從“常規”代碼中分離錯誤處理代碼(根據Java的創建者),對我來說,抓住它是合適的NullPointerException

    public Photo getGirlfriendPhoto() {
        try {
            return appContext.getPhotoDataSource().getPhotoByName(me.getGirlfriend().getName());
        } catch (NullPointerException e) {
            return null;
        }
    }
    

    可能會出現問題:

    問:如果getPhotoDataSource()返回null會怎麼樣?
    答:這取決於業務邏輯。如果我找不到相冊,我會告訴你沒有照片。如果未初始化appContext怎麼辦?這種方法的業務邏輯就是這樣。如果相同的邏輯應該更嚴格,那麼拋出異常它就是業務邏輯的一部分,應該使用null的顯式檢查(情況3)。在新的Java空安全的API更適合的位置,指定選擇性意味著什麼,什麼不意味著被初始化是快速失敗的編程錯誤的情況下。

    問:可以執行冗餘代碼並且可以獲取不必要的資源。
    答:如果getPhotoByName()嘗試打開數據庫連接,最後創建PreparedStatement並使用人名作為SQL參數,則可能會發生這種情況。針對未知問題的方法給出了未知答案(案例1)。在獲取資源之前,該方法應檢查參數並在需要時返回“未知”結果。

    問:由於嘗試關閉開放,這種方法會帶來性能損失。
    答:首先應該易於理解和修改軟件。只有在此之後,才能考慮性能,只有在需要時!並在需要的地方!(source)和許多其他人)。

    PS。這種方法使用起來也是合理的,因為“常規”代碼原則的單獨錯誤處理代碼在某些地方使用是合理的。考慮下一個例子:

    public SomeValue calculateSomeValueUsingSophisticatedLogic(Predicate predicate) {
        try {
            Result1 result1 = performSomeCalculation(predicate);
            Result2 result2 = performSomeOtherCalculation(result1.getSomeProperty());
            Result3 result3 = performThirdCalculation(result2.getSomeProperty());
            Result4 result4 = performLastCalculation(result3.getSomeProperty());
            return result4.getSomeProperty();
        } catch (NullPointerException e) {
            return null;
        }
    }
    
    public SomeValue calculateSomeValueUsingSophisticatedLogic(Predicate predicate) {
        SomeValue result = null;
        if (predicate != null) {
            Result1 result1 = performSomeCalculation(predicate);
            if (result1 != null && result1.getSomeProperty() != null) {
                Result2 result2 = performSomeOtherCalculation(result1.getSomeProperty());
                if (result2 != null && result2.getSomeProperty() != null) {
                    Result3 result3 = performThirdCalculation(result2.getSomeProperty());
                    if (result3 != null && result3.getSomeProperty() != null) {
                        Result4 result4 = performLastCalculation(result3.getSomeProperty());
                        if (result4 != null) {
                            result = result4.getSomeProperty();
                        }
                    }
                }
            }
        }
        return result;
    }
    

    PPS。對於那些快速下載(並且讀取文檔不那麼快)的人,我想說我的生活中從未發現過空指針異常(NPE)。但這種可能性是由Java創建者故意設計的,因為NPE是其子類Exception。我們在Java歷史上有一個先例,當時ThreadDeathError不是因為它實際上是一個應用程序錯誤,而僅僅是因為它不打算被抓住!多少NPE適合成為一個ErrorThreadDeath!但事實並非如此。

  3. 僅在業務邏輯暗示時才檢查“無數據”。

    public void updatePersonPhoneNumber(Long personId, String phoneNumber) {
        if (personId == null)
            return;
        DataSource dataSource = appContext.getStuffDataSource();
        Person person = dataSource.getPersonById(personId);
        if (person != null) {
            person.setPhoneNumber(phoneNumber);
            dataSource.updatePerson(person);
        } else {
            Person = new Person(personId);
            person.setPhoneNumber(phoneNumber);
            dataSource.insertPerson(person);
        }
    }
    

    public void updatePersonPhoneNumber(Long personId, String phoneNumber) {
        if (personId == null)
            return;
        DataSource dataSource = appContext.getStuffDataSource();
        Person person = dataSource.getPersonById(personId);
        if (person == null)
            throw new SomeReasonableUserException("What are you thinking about ???");
        person.setPhoneNumber(phoneNumber);
        dataSource.updatePerson(person);
    }
    

    如果未初始化appContext或dataSource,則未處理的運行時NullPointerException將Thread.defaultUncaughtExceptionHandler當前線程並將由Thread.defaultUncaughtExceptionHandler處理(您可以定義和使用您喜歡的記錄器或其他通知機制)。如果未設置,ThreadGroup#uncaughtException將stacktrace打印到系統錯誤。應該監視應用程序錯誤日誌並為每個未處理的異常打開Jira問題,這實際上是應用程序錯誤。程序員應該在初始化的東西中修復bug。


只是不要使用null。不要允許它。

在我的類中,大多數字段和局部變量都具有非空的默認值,並且我在代碼中的任何地方添加了契約語句(always-on asserts)以確保執行它(因為它更簡潔,更具表現力而不是讓它作為NPE出現,然後必須解決行號等)。

一旦我採用了這種做法,我注意到問題似乎已經解決了。你會在開發過程中更早地發現事情,並意識到你有一個弱點......更重要的是......它有助於封裝不同模塊的問題,不同的模塊可以互相“信任”,不再亂扔垃圾代碼與if = null else構造!

這是防禦性編程,從長遠來看會產生更清晰的代碼。始終清理數據,例如通過強制執行嚴格的標準,問題就會消失。

class C {
    private final MyType mustBeSet;
    public C(MyType mything) {
       mustBeSet=Contract.notNull(mything);
    }
   private String name = "<unknown>";
   public void setName(String s) {
      name = Contract.notNull(s);
   }
}


class Contract {
    public static <T> T notNull(T t) { if (t == null) { throw new ContractException("argument must be non-null"); return t; }
}

合同就像迷你單元測試一樣,即使在生產中也一直在運行,當事情失敗時,你知道為什麼,而不是隨機的NPE,你必須以某種方式弄清楚。


我試過了,NullObjectPattern但對我來說並不總是最好的方式。有時候“不採取行動”是不恰當的。

NullPointerException是一個運行時異常,這意味著它是開發人員的錯誤,並且具有足夠的經驗,它會告訴您錯誤的確切位置。

現在回答:

盡量使所有屬性及其訪問者盡可能保密,或者避免將它們暴露給客戶端。當然,您可以在構造函數中包含參數值,但是通過減小範圍,您不會讓客戶端類傳遞無效值。如果需要修改值,可以隨時創建新值object。您只檢查構造函數中的值一次,並且在其餘方法中幾乎可以確定值不為null。

當然,經驗是理解和應用此建議的更好方式。

字節!


我高度忽略了建議在每種情況下使用null對象的答案。這種模式可能會破壞合同並將問題深入和深入地埋沒而不是解決它們,沒有提到使用不當會產生另一堆樣板代碼,這將需要將來的維護。

實際上,如果從方法返回的內容可以為null並且調用代碼必須對此做出決定,那麼應該有一個確保狀態的早期調用。

還要記住,如果毫無顧慮地使用空對像模式將是內存飢餓。為此 - 應該在所有者之間共享NullObject的實例,而不是每個實例的unigue實例。

此外,我不建議使用此模式,其中類型是基本類型表示 - 如數學實體,不是標量:向量,矩陣,複數和POD(普通舊數據)對象,它們旨在保持狀態以Java內置類型的形式。在後一種情況下,您最終會調用具有任意結果的getter方法。例如,NullPerson.getName()方法應返回什麼?

值得考慮這些案例以避免荒謬的結果。


除了使用之外,assert您還可以使用以下內容:

if (someobject == null) {
    // Handle null here then move on.
}

這略好於:

if (someobject != null) {
    .....
    .....



    .....
}

public static <T> T ifNull(T toCheck, T ifNull) {
    if (toCheck == null) {
           return ifNull;
    }
    return toCheck;
}




null