先假設一個場景。如下所示
1 public class Person { 2 3 private String name; 4 5 public Person() { 6 } 7 8 public Person(String name) { 9 this.name = name; 10 } 11 12 public String getName() { 13 return name; 14 } 15 }
我們有一個Person類,有一個屬性是name。有如下代碼:
1 public static void main(String[] args) { 2 Person person = new Person(); 3 4 String name = person.getName(); 5 6 if (name.equals("Jackson")) { 7 System.out.println("My name is Jackson!"); 8 } 9 }
我們判斷獲取person的name,然后判斷person的name是不是Jackson。因為person的name為null,所以name.equals("Jackson")
這段代碼會報空指針的異常。
我們有多種方式來處理空指針的異常,一種是我們考察我們的業務邏輯,當需要返回一個null值時返回一個有意義的NullObject。例如空字符串對應返回字符串的情況,空列表對應返回list的情況,對一些特殊的對象,可以根據不同的業務邏輯創建一些Null對象。參考《Effictive Java》第43條,《重構》9.7節。
對於我們的例子,當name為null時,getName方法返回一個""
字符串。當接下來取出name來進行操作時就不會出現空指針的異常。
1 public String getName() { 2 if (name == null) { 3 return ""; 4 } else { 5 return name; 6 } 7 }
另一種是在調用的時候判斷是否為空,對於我們的例子,如下代碼:
1 if (name != null && name.equals("Jackson")){}
通過判斷name是否為空,若非空再判斷name是否等於Jackson。通過這樣的方式可以解決空指針異常的問題。
但是兩種方案都有一定的缺點,對於第一種方案,String和集合類型的Null對象很好確認,就是空字符串和空集合。但是對於一些特殊的對象,創建Null對象需要更多的步驟,更多的設計。總之就是兩個字,麻煩。
對於第二種方案,這個也是我們最常用的。但是這個方案有一個問題,就是經常會忘記判空。對於一個方法other.method(a, b)
,我們常常不會忘記對a和b進行判空,而對方法的返回值卻經常忘記判空。而這個空指針異常是在運行時才會被發現,如果空指針的情況十分的罕見,那么很可能很長一段時間都不會發現這個異常。我常常碰到的一種情況是當數據庫中存在臟數據的時候,正常的數據是不應該存在null的,但是即使是生產環境也很有可能混入一兩個臟數據,很可能這罕見的臟數據導致程序報錯。可能這個時候再去修復bug,代價比開發階段高很多。
為了優雅地處理null的情況,可以使用Java8的新特性Optional。API不說了,直接上代碼
1 public class Person { 2 3 private String name; 4 5 public Person() { 6 } 7 8 public Person(String name) { 9 this.name = name; 10 } 11 12 public Optional<String> getName() { 13 return Optional.ofNullable(name); 14 } 15 16 public static void main(String[] args) { 17 Person person = new Person(); 18 19 Optional<String> optionalName = person.getName(); 20 if (optionalName.isPresent()) { 21 String name = optionalName.get(); 22 System.out.println("My name is :" + name); 23 } else { 24 System.out.println("Who am I?"); 25 } 26 } 27 }
在getName里,我們由原來直接返回name,改成返回Optional<String>。通過Optional.ofNullable(T)靜態方法,可以創建一個Optional對象。如果傳入的對象是null,那么isPresent返回false,否則返回true。在上訴代碼中,Person的name沒有設置,optionalName.isPresent()返回的是false,所以打印出的應該是Who am I?
。
從代碼上看,這個和第二種方案,對對象判空的方法沒有區別,都是先判斷是否存在,然后再對對象進行操作。因為我們沒法直接獲取所需的對象,而是獲得Optional對象,需要從Optional中獲取我們需要的對象,因為不判空取不到所需對象,所以我們就不會忘了判空這步操作了。