NPE問題,100%的Java程序員都碰到,而且曾經是心中的痛。
1965年英國TonyHoare引入了Null引用,後續的設計語言包括Java都保持了這種設計。html
業務模型java
Person 有車一族, 有Car字段,程序員
Car 車,每一個車都有購買保險, 有Insurance字段;編程
Insurance 保險,每一個保險都有名字 有name字段;api
需求:獲取某個Person對象的購買保險的名稱;ide
常規編程函數
public String getCarInsuranceName(Person person) { return person.getCar().getInsurance().getName(); }
檢查式編程性能
public String getCarInsuranceName_check(Person person) { if (Objects.nonNull(person)) { final Car car = person.getCar(); if (Objects.nonNull(car)) { final Insurance insurance = car.getInsurance(); if (Objects.nonNull(insurance)) { return insurance.getName(); } } } return "unkown"; }
防護式編程this
public String getCarInsuranceName_protect(Person person) { if (Objects.isNull(person)) { return "unkown"; } final Car car = person.getCar(); if (Objects.isNull(car)) { return "unkown"; } final Insurance insurance = car.getInsurance(); if (Objects.isNull(insurance)) { return "unkown"; } return insurance.getName(); }
對比一下缺點:設計
編程方法 | 缺點 |
---|---|
常規編程 | NPE問題 |
檢查式編程 | 1.可讀性很差,多層if嵌套; 2.擴展性很差,須要熟悉全流程,不然不知道應該在哪一個if中擴展,極易出錯; |
防護式編程 | 1. 維護困難,4個不一樣的退出點,極易出錯,容易遺漏檢查項目 |
NPE的痛點
- java程序中出現最多的Exception;沒有之一;
- 使得代碼量膨脹混亂,對象的空判斷充斥在代碼中,可是卻沒有實際的業務意義;
- 類型系統的一個後門,實際上不屬於任何類型,也能夠說是任何類型;
- 自己無心義,標識對缺失值的建模,也破壞了java中弱化指針的理念。
java8中對缺失值的建模對象是Optional,能夠基於它解決NPE的痛點,設計更好的API
領域模型的建模進化
構造方法 | 說明 | 備註 |
---|---|---|
Optional.empty() | 必定是空的對象 | 跟null有區別,是一個單例對象 |
Optional.of(T t) | 必定是不空的對象 | 若是給了null值會馬上拋出NPE |
Optioanl.ofNullable(T t) | 容許爲空的對象放在裏面 | 使用值以前須要作檢查 |
能夠把Optional當作一種單元素的Stream, Map,即把其中的元素按照必定規則轉換爲其它類型或者進行其它運算後的值,若是沒有元素,則啥也不作。
下面的代碼是等同的。
public class Test { public static final String UNKNOWN = "unknown"; /** * 傳統方法 * @param insurance * @return */ public static String getInsuranceName(Insurance insurance){ if (Objects.isNull(insurance)){ return UNKNOWN; } return insurance.getName(); } /** * map的方式提取 * @param insurance * @return */ public static String getInsuranceNameOp(Insurance insurance){ return Optional.ofNullable(insurance).map(Insurance::getName).orElse(UNKNOWN); } }
相似於Stream的flatMap方法,把元素切割或者組合成另一個流輸出。
public static String getInsuranceName(Person person) { return Optional.ofNullable(person) .flatMap(Person::getCar) .flatMap(Car::getInsurance) .map(Insurance::getName).orElse(UNKNOWN); }
默認值方法 | 說明 | 場景 |
---|---|---|
or(Supplier
|
爲空則延遲構造一個Optional對象 | 能夠採用延遲的方式,對接某些代碼來產生默認值 |
orElse(T t) | 爲空則採用默認值 | 直接,簡單 |
orElseGet(Supplier
|
爲空則經過函數返回 | 延遲返回,能夠對接某些代碼邏輯 |
orElseThrow() | 爲空則跑出異常 | 默認是NoSuchElementException |
orElseThrow(Supplier
|
爲空則跑出自定義異常 | 異常類型能夠自定義 |
主要分兩種場景,直接獲取值, 採用get()方法;
裏面有值,則消費, ifPresent(Consumer
c)
消費或者獲取方法 | 說明 | 場景 |
---|---|---|
get() | 獲取Optional中的值,若是沒有值,會拋出異常 | 確認裏面有值纔會調用該防範 |
ifPresent(Consumer
|
有值則執行自定義代碼段,消費該值 | 流式編程,有值繼續處理邏輯 |
ifPresentOrElse(Consumer
|
若是有值,則消費,沒有值,進行另外的處理 | 有值或者沒有值都進行處理java9纔有 |
經過使用flatMap,map能夠作到,方法裏執行的已經作好了對empty的狀況進行處理。
實例以下:
public static String getCheapestPrizeIsuranceNameOp(Person person, Car car) { return Optional.ofNullable(person) .flatMap(p -> Optional.ofNullable(car).map(c -> getCheapest(p, c))) .orElse(UNKNOWN); } public static String getCheapestPrizeIsuranceName(Person person, Car car) { if (Objects.nonNull(person) && Objects.nonNull(car)) { return getCheapest(person, car); } return UNKNOWN; } /** * 模擬獲得最便宜的保險 * * @param person 人 * @param car 車 * @return 最便宜的車險名稱 */ private static String getCheapest(Person person, Car car) { return "pinan"; }
由於Optional中只有一個值,因此這裏的filter其實是判斷單個值是否是。
對比代碼:
public static Insurance getPinanInsurance(Person person){ Optional<Insurance> insuranceOptional = Optional.ofNullable(person).map(Person::getCar).map(Car::getInsurance); if (insuranceOptional.isPresent() && Objects.equals("pinan", insuranceOptional.get().getName())){ return insuranceOptional.get(); } return null; } public static Insurance getPinanInsurance_filter(Person person){ return Optional.ofNullable(person) .map(Person::getCar) .map(Car::getInsurance) .filter(item->Objects.equals(item.getName(),"pinan" )) .orElse(null); }
public Object getFromMap(String key){ Map<String,Object> map = new HashMap<>(4); map.put("a", "aaa"); map.put("b", "bbb"); map.put("c", "ccc"); Object value = map.get(key); if (Objects.isNull(value)){ throw new NoSuchElementException("不存在key"); } return value; } public Object getFromMapOp(String key){ Map<String,Object> map = new HashMap<>(4); map.put("a", "aaa"); map.put("b", "bbb"); map.put("c", "ccc"); Object value = map.get(key); return Optional.ofNullable(value).orElseThrow(()->new NoSuchElementException("不存在key")); }
這種是建模思想的轉變,不必定適用每一個人;
/** * 若是字符串不是數字,會拋出異常 * @param a 字符串 * @return 數字 */ public Integer string2Int(String a){ return Integer.parseInt(a); } /** * Optional.empty對應異常的狀況,後續比較好處理; * @param a 字符串 * @return 可能轉換失敗的整數,延遲到使用方去處理 */ public Optional<Integer> string2Int_op(String a){ try{ return Optional.of(Integer.parseInt(a)); }catch (Exception ex){ return Optional.empty(); } }
封裝的OptionalInt, OptionalLong ,由於Optional裏面只有一個元素,使用封裝類沒有性能優點,並且缺失了重要的flatMap, map,filter方法;
總的來講,Optional的使用,簡化了代碼,使得代碼可讀性和可維護性更好。
最後來個例子:
public Integer getFromProperties(Properties properties, String key) { String value = properties.getProperty(key); if (Objects.nonNull(value)) { try { Integer integer = Integer.parseInt(value); if (integer > 0) { return integer; } } catch (Exception ex) { //無需處理異常 return 0; } } return 0; } public Integer getFromProperties_op(Properties properties, String key) { return Optional.ofNullable(properties.getProperty(key)) .map(item -> { try { return Integer.parseInt(item); } catch (Exception ex) { return 0; } }) .orElse(0); }
一個容器對象,可能有也可能沒有非空值,若是值存在,isPresent()返回true,若是沒有值,則對象被當成空,isPresent()返回false; 更多的方法依賴於容器中是否含有值,好比orElse(返回一個默認值當沒有值) ifPresent(Consumer c) 是當值存在的時候,執行一個動做; 這是一個基於值的類,使用標識敏感的操做,包含 比較引用的 == , hashcode , synchronization 針對一個Optional對象,可能有沒法預料的結果,而後應該避免這類操做。 編寫API的注意點: Optional最初被用來設計爲方法的返回值,當明確須要表明沒有值的狀況。 返回null,可能出錯;而返回Optional對象不是一個null對象,它老是指向一個Optional對象實例。 /** * A container object which may or may not contain a non-{@code null} value. * If a value is present, {@code isPresent()} returns {@code true}. If no * value is present, the object is considered <i>empty</i> and * {@code isPresent()} returns {@code false}. * * <p>Additional methods that depend on the presence or absence of a contained * value are provided, such as {@link #orElse(Object) orElse()} * (returns a default value if no value is present) and * {@link #ifPresent(Consumer) ifPresent()} (performs an * action if a value is present). * * <p>This is a <a href="../lang/doc-files/ValueBased.html">value-based</a> * class; use of identity-sensitive operations (including reference equality * ({@code ==}), identity hash code, or synchronization) on instances of * {@code Optional} may have unpredictable results and should be avoided. * * @apiNote * {@code Optional} is primarily intended for use as a method return type where * there is a clear need to represent "no result," and where using {@code null} * is likely to cause errors. A variable whose type is {@code Optional} should * never itself be {@code null}; it should always point to an {@code Optional} * instance. * * @param <T> the type of value * @since 1.8 */
其它的代碼比較簡單,模型就是裏面含有一個T類型的值,empty()是一個特殊的Optional對象,裏面的值是null;
public final class Optional<T> { /** * Common instance for {@code empty()}. */ private static final Optional<?> EMPTY = new Optional<>(); /** * If non-null, the value; if null, indicates no value is present */ private final T value; /** * Constructs an empty instance. * * @implNote Generally only one empty instance, {@link Optional#EMPTY}, * should exist per VM. */ private Optional() { this.value = null; }
- Optional表示一個可能缺失的對象,API能夠依據這個進行建模,可是要注意序列化的問題;能夠避免空指針的問題,而且提高代碼的可讀性和可維護性。
- Optional的構造方法有3個,of,ofNullable,empty;
- map,flatmap , filter 能夠快速的轉換和過濾值;
- 值缺失的處理方法有3個,orElse, orElseGet, orElseThrow;
原創不易,轉載請註明出處。