1、使用object==null的例子
2、null帶來的問題
3、其他語言中null的處理(替代)
4、Java8的Optional類
4.1 這樣做有什么好處呢?
4.2 引入Optional類的目的
4.3 null與Optional.empty()
4.4 使用Optional
4.5 使用Optional域,該域無法序列化
4.6 應用
參考文獻
1、使用object==null的例子
例1
pulbic String getCarInsuranceName(Person person){ if(person != null){ Car car = person.getCar(); if(car != null){ Insurance insurance = car.getInsurance(); if(insurance != null){ return insurance.getName(); } } } return "UNKNOWN"; }
可以發現這樣寫比較繁瑣,每當某個變量可能為null時,都要添加if塊來判斷,增加了代碼的縮進級別,擴展性和可讀性都很差,且萬一忘記判斷某個變量,依然出現NPE。
例2
public String getCarInsuranceName(Person person){ if(person == null){ return "unknown"; } Car car = person.getCar(); if(car == null){ return "unknown"; } Insurance insurance = car.getInsurance(); if(insurance == null){ return "unknown"; } return insurance.getName(); }
這種方式避免深層嵌套的if塊,但是每一個null檢查點都增加一個退出點,難以維護,且為null時,返回的字符串“unknown”重復出現。同樣的,當忘記檢查某個可能為null的屬性時,會出現NPE。
2、null帶來的問題
NPE是目前Java程序開發種最典型的異常; 會使代碼膨脹,深度嵌套的null檢查,代碼的可讀性差; null自身是毫無意義的,null沒有任何語義; 破壞了Java的哲學,Java一直試圖避免讓程序員意識到指針,唯一的例外就是null指針; null在Java的類型系統上成了例外,null不屬於任何類型,即它可以賦值給任意引用類型,當這個變量被傳遞到系統的另一個部分后,將無法獲知這個null變量最初的賦值到底什么類型。 |
3、其他語言中null的處理(替代)
Groovy:安全導航操作符(safe navigation operator, 標記為?)
e.g. def carInsuranceName = person?.car?.insurance?.name 當調用鏈中的變量遇到null時將null引用沿着調用鏈傳遞下去,返回一個null。
函數式語言:
Haskell:Maybe類型,其本質上是對optional值的封裝。Maybe類型的變量可以是指定類型的值,也可以什么都不是。沒有null引用的概念。
Scala:Option[T],既可以包含類型為T的變量,也可以不包含該變量,顯式調用Option類型的available操作,檢查該變量是否有值,即變相的“null”檢查。
4、Java8的Optional類
Java8引入Optional類,在從null到Optional的遷移中,需要反思的是:如何在你的域模型中使用Optional值。
我們根據上面例子的類關系可知,Person的Car變量存在null情況,因此不能直接聲明為Car,改為Optional<Car>。
4.1 這樣做有什么好處呢?
使用Optional<Car>聲明變量,清楚的表明了這里的變量缺失是允許的,而使用Car類型,可能將變量賦值為null,就只能依賴業務模型的理解,判斷一個null是否屬於該變量的有效范疇。
我們根據Optioanl類重新定義Person/Car/Insurance的數據模型:
public class Person{ private Optional<Car> car; public Optional<Car> getCar() {return car;} } public class Car{ private Optional<Insurance> insurance; public Optional<Insurance> getInsurance(){return insurance;} } public class Insurance{ private String name; // 保險公司必須有名字 public String getName(){return name;} }
當獲取insurance公司名稱發生NPE,就能非常確定出錯的原因,不需要為其添加null的檢查,因為null的檢查只會掩蓋問題,並未真正地修復問題。insurance公司必須有名字,當公司沒有名稱,就需要查看出了什么問題,而不應該再添加一段代碼,將這個問題隱藏。
4.2 引入Optional類的目的
代碼中始終如一使用Optional,能非常清晰地節點出變量值的缺失是結構上的問題還是算法上或是數據中的問題。
引入Optional並不是要消除每一個null引用,而是幫助設計更普適的API,看見簽名就能了解是否接受一個Optional的值,這種強制會讓你更積極地將變量從Optional中解包出來,直面缺失的變量值。
即看見Optional<Car>就可以知道該變量car可能為null。
4.3 null與Optional.empty()
嘗試解引用一個null,一定會觸發NPE,而使用Optional.empty()就沒事,其為Optioanl類的一個有效對象,多種場景都能調用,非常有用。
Optional.empty()的定義如下
/** * Returns an empty {@code Optional} instance. No value is present for this * Optional. * * @apiNote Though it may be tempting to do so, avoid testing if an object * is empty by comparing with {@code ==} against instances returned by * {@code Option.empty()}. There is no guarantee that it is a singleton. * Instead, use {@link #isPresent()}. * * @param <T> Type of the non-existent value * @return an empty {@code Optional} */ public static<T> Optional<T> empty() { @SuppressWarnings("unchecked") Optional<T> t = (Optional<T>) EMPTY; return t; }
EMPTY的定義如下:
/** * Common instance for {@code empty()}. */ private static final Optional<?> EMPTY = new Optional<>();
4.4 使用Optional
(1) 使用map從Optional對象中提取和轉換值
// 原先寫法 String name = null; if (insurance != null){ name = insurance.getName(); } // 使用Optional Optional<Insurance> optInsurance = Optional.ofNullable(insurance); Optional<String> name = optInsurance.map(Insurance::getName);
stream和map方法:
(2)使用flatMap方法連接Optional對象
// 原先寫法 public String getCarInsuranceName(Person person){ return person.getCar().getInsurance().getName(); } // 使用Optional.map會報錯 /*因為Person的類型是Optional<Computer>,調用map方法是正常的,
但是,getCar()方法返回的是一個Optional<Car>對象,
即這個map操作后得到的類型是Optional<Optional<Car>>.
對一個Optional對象調用getInsurance ()是非法的。*/ public String getCarInsuranceName(Optional<Person> person){ return person.map(Person::getCar) .map(Car::getInsurance) .map(Insurance::getName) .orElse("UNKNOWN"); } // 使用Optional.flatMap public String getCarInsuranceName(Optional<Person> person){ return person.flatMap (Person::getCar) .flatMap(Car::getInsurance) .map(Insurance::getName) .orElse("UNKNOWN"); }
stream和flatMap方法比較:
flatMap方法連接Optional對象過程:
(3)map和flatMap方法源碼
public<U> Optional<U> map(Function<? super T, ? extends U> mapper) { Objects.requireNonNull(mapper); if (!isPresent()) return empty(); else { return Optional.ofNullable(mapper.apply(value)); // Optional.ofNullable()會返回一個Optional<> } } //如果調用的mapper返回已經是Optional,則調用該mapper后,flatMap不會再添加Optional public<U> Optional<U> flatMap(Function<? super T, Optional<U>> mapper) { Objects.requireNonNull(mapper); if (!isPresent()) return empty(); else { return Objects.requireNonNull(mapper.apply(value)); //返回參數的類型 } }
Objects類的requireNonNull方法:
public static <T> T requireNonNull(T obj) { if (obj == null) throw new NullPointerException(); return obj; }
4.5 使用Optional域,該域無法序列化
在域模型中使用Optional,該域無法序列化,因為Optional沒有實現Serializable接口。
Java語言的架構師Brain Goetz明確陳述過:Optional的設計初衷僅僅是要支持能返回Optional對象的語法。
Optional類設計時就沒有特別考慮將其作為類的字段使用,所以它並未實現Serializable接口。
若一定要序列化域,替代方案:
public class Person{ private Car car; public Optional<Car> getCarOptional(){ return Optional.ofNullable(car); } }
4.6 應用
(1)用Optional封裝可能為null的值
Object value = map.get("key");
↓↓
Optional<Object> value = Optional.ofNullable(map.get("key"));
(2)異常
Integer.parseInt(String) --> NumberFormatException ↓↓ public static Optional<Integer> stringToInt(String s){ try{ return Optional.of(Integer.parseInt(s)); } catch(NumberFormatException e){ return Optional.empty(); } }
(3)基礎類型的Optonal對象
Optional也提供了基礎類型--OptionalInt, OptionalLong, OptionalDouble,但是不推薦使用,因為基礎類型的Optional不支持map、flatMap以及filter方法。
(4)整合
Properties props = new Properties(); props.setProperty("a", "5"); props.setProperty("b", "true"); props.setProperty("c", "-3");
需求:從這些屬性中讀取一個值,該值的含義是時間,單位s,所以必須≥0.
public int readDuration(Properties props, String name){ String value = props.getProperty(name); if (value != null){ try{ int i = Integer.parseInt(value); if (i > 0){ return i; } }catch(NumberFormatException nfe){} } return 0; } ↓↓ public int readDuration(Properties props, String name){ return Optional.ifNullable(props.getProperty(name)) .flatMap(OptionalUtil::stringToInt) // ②中的方法 .filter(i -> i > 0) .orElse(0); }
參考文獻:《Java8實戰》(英)Urma, R.G. (意)Fusco, M. (英)Mycroft, A,著;陸明剛 勞佳譯. 北京:人民郵電出版社,2016.4
官網:https://www.oracle.com/technical-resources/articles/java/java8-optional.html