沒人會喜歡空指標異常!有什麼方法可以避免它們嗎?或許吧。。
本文將討論到以下幾種技術
- Optional型別(Java 8中新引入的)
- Objects類(Java 7中原有的)
Java 8中的Optional類
它是什麼?
- Java 8中新引入的型別
- 它是作為某個指定型別的物件的包裝器或者用於那些不存在物件(null)的場景
簡單來說,它是處理空值的一個更好的替代品(警告:乍一看可能並沒有那麼明顯)
基本用法
它是一種型別(一個類)——那麼,怎麼才能建立一個這個型別的例項?
使用下它的三個靜態方法就可以了
1 2 3 |
public static Optional<String> stringOptional(String input) { return Optional.of(input); } |
簡單明瞭——建立一個包含這個值的Optional包裝器。記住——如果這個值是null的話,它會丟擲NPE!
1 2 3 4 5 6 |
public static Optional<String> stringNullableOptional(String input) { if (!new Random().nextBoolean()) { input = null; } return Optional.ofNullable(input); } |
我個人認為是要更好一點。這樣就不會有NPE的風險了——如果輸入為null的話,會返回一個空的Optional。
1 2 3 |
public static Optional<String> emptyOptional() { return Optional.empty(); } |
如果你真的就是希望返回一個”空”值的話。“空”值並不意味著null。
好吧,那如何去消費/使用Optional呢?
1 2 3 4 5 6 7 8 9 |
public static void consumingOptional() { Optional<String> wrapped = Optional.of("aString"); if (wrapped.isPresent()) { System.out.println("Got string - " + wrapped.get()); } else { System.out.println("Gotcha !"); } } |
簡單的方法就是檢查Optional包裝器是否真的有值(使用isPresent方法)——你會懷疑這和使用if(myObj != null)相比有什麼好處。別擔心,這個我會解釋清楚的
1 2 3 4 5 6 7 8 |
public static void consumingNullableOptional() { String input = null; if (new Random().nextBoolean()) { input = "iCanBeNull"; } Optional<String> wrapped = Optional.ofNullable(input); System.out.println(wrapped.orElse("default")); } |
你可以使用orElse方法,這樣萬一封裝的確實是一個null值的話可以用它來返回一個預設值——它的好處顯而易見。在提取出真實值的時候可以避免呼叫ifPresent方法這樣明顯多餘的方式了。
1 2 3 4 5 6 7 8 9 10 11 12 13 |
public static void consumingEmptyOptional() { String input = null; if (new Random().nextBoolean()) { input = "iCanBeNull"; } Optional<String> wrapped = Optional.ofNullable(input); System.out.println(wrapped.orElseGet( () -> { return "defaultBySupplier"; } )); } |
這個我就有點搞不清楚了。為什麼有兩個同樣目的的不同方法?orElse和orElseGet明明可以過載的(同名但不同引數)。
不論如何,這兩個方法明顯的區別就在於它們的引數——你可以選擇使用lambda表示式而不是Supplier的例項來完成這個(一個函式式介面)
為什麼使用Optional要比常見的null檢查強?
- 使用Optional最大的好處就是可以更明白地表述你的意圖——返回null值的話會讓消費者感到疑惑(當真的出現NPE的時候)這是不是故意返回的,因此還得檢視javadoc來進一步定位。而使用Optional就相當明瞭了。
- 有了Optional你就可以徹底避免NPE了——如上所提,使用Optional.ofNullable,orElse以及orElseGet可以讓我們遠離NPE。
另一個救星!
看下這個程式碼片段
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
package com.abhirockzz.wordpress.npesaviors; import java.util.Map; import java.util.Objects; public class UsingObjects { String getVal(Map<String, String> aMap, String key) { return aMap.containsKey(key) ? aMap.get(key) : null; } public static void main(String[] args) { UsingObjects obj = new UsingObjects(); obj.getVal(null, "dummy"); } } |
哪個可能會為空?
- Map物件
- 進行搜尋使用的key
- 方法呼叫的這個例項
如果丟擲NPE的話,我們怎麼能確定到底是哪個是null的?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
package com.abhirockzz.wordpress.npesaviors; import java.util.Map; import java.util.Objects; public class UsingObjects { String getValSafe(Map<String, String> aMap, String key) { Map<String, String> safeMap = Objects.requireNonNull(aMap, "Map is null"); String safeKey = Objects.requireNonNull(key, "Key is null"); return safeMap.containsKey(safeKey) ? safeMap.get(safeKey) : null; } public static void main(String[] args) { UsingObjects obj = new UsingObjects(); obj.getValSafe(null, "dummy"); } } |
requireNonNull方法
- 如果物件不為null的話就返回它本身
- 如果值為null的話,返回的NPE會帶有指定的訊息
為什麼比if(myObj!=null)要好?
你所看到的棧跟蹤資訊會很清楚地看見Objects.requireNonNull的方法呼叫。這個再配合你自己的錯誤日誌,可以讓你更快地定位問題。。。至少在我看來是更快。
你還可以自己自義校驗器,比如說實現一個簡單的校驗器來確保沒有空值
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 |
import java.util.Collections; import java.util.List; import java.util.Objects; import java.util.function.Predicate; public class RandomGist { public static <T> T requireNonEmpty(T object, Predicate<T> predicate, String msgToCaller){ Objects.requireNonNull(object); Objects.requireNonNull(predicate); if (predicate.test(object)){ throw new IllegalArgumentException(msgToCaller); } return object; } public static void main(String[] args) { //Usage 1: an empty string (intentional) String s = ""; System.out.println(requireNonEmpty(Objects.requireNonNull(s), (s1) -> s1.isEmpty() , "My String is Empty!")); //Usage 2: an empty List (intentional) List list = Collections.emptyList(); System.out.println(requireNonEmpty(Objects.requireNonNull(list), (l) -> l.isEmpty(), "List is Empty!").size()); //Usage 3: an empty User (intentional) User user = new User(""); System.out.println(requireNonEmpty(Objects.requireNonNull(user), (u) -> u.getName().isEmpty(), "User is Empty!")); } private static class User { private String name; public User(String name){ this.name = name; } public String getName(){ return name; } } } |
不要讓NPE在錯誤的地方成為痛苦。我們有許多工具能更好地處理NPE,甚至徹底地根除它們!