1965年,英國一位名爲Tony Hoare的計算機科學家在設計ALGOL W語言時提出了null引用的想法。ALGOL W是第一批在堆上分配記錄的類型語言之一。Hoare選擇null引用這種方式,「只是由於這種方法實現起來很是容易」。雖然他的設計初衷就是要「經過編譯器的自動檢測機制,確保全部使用引用的地方都是絕對安全的」,他仍是決定爲null引用開個綠燈,由於他認爲這是爲「不存在的值」建模最容易的方式。不少年後,他開始爲本身曾經作過這樣的決定然後悔不已,把它稱爲「我價值百萬的重大事物」。實際上,Hoare的這段話低估了過去五十年來數百萬程序員爲修復空引用所耗費的代價。近十年出現的大多數現代程序設計語言1,包括Java,都採用了一樣的設計方式,其緣由是爲了與更老的語言保持兼容,或者就像Hoare曾經陳述的那樣,「僅僅是由於這樣實現起來更加容易」。
public class Person { private Car car; public Car getCar() { return car; } } public class Car { private Insurance insurance; public Insurance getInsurance() { return insurance; } } public class Insurance { private String name; public String getName() { return name; } } public String getCarInsuranceName(Person person) { return person.getCar().getInsurance().getName(); }
上面這段代碼的問題就在於,若是person沒有車,就會形成空指針異常。java
簡單來講就是在須要的地方添加null檢查程序員
public 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"; }
上述代碼不具有擴展性,同時還犧牲了代碼的可讀性。segmentfault
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(); }
這種模式中方法的退出點有四處,使得代碼的維護異常艱難。安全
變量存在時,Optional類只是對類簡單封裝。變量不存在時,缺失的值會被建模成一個「空」的Optional對象,由方法Optional.empty()返回。Optional.empty()方法是一個靜態工廠方法,它返回Optional類的特定單一實例。函數
引入Optional類的意圖並不是要消除每個null引用,相反的是,它的目標是幫助開發者更好地設計出普適的API。工具
正如前文已經提到,你能夠經過靜態工廠方法Optional.empty,建立一個空的Optional對象:性能
Optional<Car> optCar = Optional.empty();
你還可使用靜態工廠方法Optional.of,依據一個非空值建立一個Optional對象:優化
Optional<Car> optCar = Optional.of(car);
若是car是一個null,這段代碼會當即拋出一個NullPointerException,而不是等到你試圖訪問car的屬性值時才返回一個錯誤。spa
最後,使用靜態工廠方法Optional.ofNullable,你能夠建立一個容許null值的Optional對象:設計
Optional<Car> optCar = Optional.ofNullable(car);
若是car是null,那麼獲得的Optional對象就是個空對象。
從對象中提取信息是一種比較常見的模式。
String name = null; if(insurance != null){ name = insurance.getName(); } 爲了支持這種模式,Optional提供了一個map方法。 Optional<Insurance> optInsurance = Optional.ofNullable(insurance); Optional<String> name = optInsurance.map(Insurance::getName);
使用流時,flatMap方法接受一個函數做爲參數,這個函數的返回值是另外一個流。 這個方法會應用到流中的每個元素,最終造成一個新的流的流。可是flagMap會用流的內容替換每一個新生成的流。換句話說,由方法生成的各個流會被合併或者扁平化爲一個單一的流。
public String getCarInsuranceName(Optional<Person> person) { return person.flatMap(Person::getCar) .flatMap(Car::getInsurance) .map(Insurance::getName) .orElse("Unknown"); }
public Optional<Insurance> nullSafeFindCheapestInsurance(Optional<Person> person, Optional<Car> car) { if (person.isPresent() && car.isPresent()) { return Optional.of(findCheapestInsurance(person.get(), car.get())); } else { return Optional.empty(); } }
filter方法接受一個謂詞做爲參數。若是Optional對象的值存在,而且它符合謂詞的條件, filter方法就返回其值;不然它就返回一個空的Optional對象。
Insurance insurance = ...; if(insurance != null && "CambridgeInsurance".equals(insurance.getName())){ System.out.println("ok」); } Optional<Insurance> optInsurance = ...; optInsurance.filter(insurance -> "CambridgeInsurance".equals(insurance.getName())) .ifPresent(x -> System.out.println("ok"));
Optional類中的方法進行了分類和歸納:
Optional<Object> value = Optional.ofNullable(map.get("key"));
每次你但願安全地對潛在爲null的對象進行轉換,將其替換爲Optional對象時,均可以考慮使用這種方法。
public static Optional<Integer> stringToInt(String s) { try { return Optional.of(Integer.parseInt(s)); } catch (NumberFormatException e) { return Optional.empty(); } }
咱們的建議是,你能夠將多個相似的方法封裝到一個工具類中,讓咱們稱之爲OptionalUtility。經過這種方式,你之後就能直接調用OptionalUtility.stringToInt方法,將String轉換爲一個Optional<Integer>對象,而再也不須要記得你在其中封裝了笨拙的 try/catch的邏輯了。
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.ofNullable(props.getProperty(name)) .flatMap(OptionalUtility::stringToInt) .filter(i -> i > 0) .orElse(0); }
這一章中,你學到了如下的內容。