前面在介紹清單用法的時候,講到了既能使用for循環遍歷清單,也能經過stream流式加工清單。譬如從一個蘋果清單中挑選出紅蘋果清單,採起for循環和流式處理均可以實現。下面是經過for循環挑出紅蘋果清單的代碼例子:html
// 經過簡單的for循環挑出紅蘋果清單 private static void getRedAppleWithFor(List<Apple> list) { List<Apple> redAppleList = new ArrayList<Apple>(); for (Apple apple : list) { // 遍歷現有的蘋果清單 if (apple.isRedApple()) { // 判斷是否爲紅蘋果 redAppleList.add(apple); } } System.out.println("for循環 紅蘋果清單=" + redAppleList.toString()); }
至於經過流式處理挑出紅蘋果清單的代碼示例以下:java
// 經過流式處理挑出紅蘋果清單 private static void getRedAppleWithStream(List<Apple> list) { // 挑出紅蘋果清單 List<Apple> redAppleList = list.stream() // 串行處理 .filter(Apple::isRedApple) // 過濾條件。專門挑選紅蘋果 .collect(Collectors.toList()); // 返回一串清單 System.out.println("流式處理 紅蘋果清單=" + redAppleList.toString()); }
然而上述的兩段代碼只能在數據完整的狀況下運行,一旦原始的蘋果清單存在數據缺失,則兩段代碼均沒法正常運行。例如,蘋果清單爲空,清單中的某條蘋果記錄爲空,某個蘋果記錄的顏色字段爲空,這三種狀況都會致使程序遇到空指針異常而退出。看來編碼不是一件輕鬆的活,不但要讓程序能跑通正確的數據,並且要讓程序對各類非法數據應對自如。換句話說,程序要足夠健壯,要擁有適當的容錯性,即便是吃錯藥了,也要可以自動吐出來,而不是硬吞下去結果一病不起。對應到挑選紅蘋果的場合中,則需層層遞進判斷原始蘋果清單的數據完整性,假若發現任何一處的數據存在缺漏狀況(如出現空指針),就跳過該處的數據處理。因而在for循環先後添加了空指針校驗的紅蘋果挑選代碼變成了下面這樣:程序員
// 在for循環的內外添加必要的空指針校驗 private static void getRedAppleWithNull(List<Apple> list) { List<Apple> redAppleList = new ArrayList<Apple>(); if (list != null) { // 判斷清單非空 for (Apple item : list) { // 遍歷現有的蘋果清單 if (item != null) { // 判斷該記錄非空 if (item.getColor() != null) { // 判斷顏色字段非空 if (item.isRedApple()) { // 判斷是否爲紅蘋果 redAppleList.add(item); } } } } } System.out.println("加空指針判斷 紅蘋果清單=" + redAppleList.toString()); }
因而可知修改後的for循環代碼一共增長了三個空指針判斷,可是上面代碼明顯太複雜了,沒必要說層層嵌套的條件分支,也沒必要說屢次縮進的代碼格式,單單說後半部分的數個右花括號,簡直叫人看得眼花繚亂,難以分清哪一個右花括號究竟對應上面的哪一個流程控制語句。這種狀況實在考驗程序員的眼力,要是一不留神看走眼放錯其它代碼的位置,豈不是撿了芝麻丟了西瓜?
空指針的校驗代碼當然繁瑣,倒是萬萬少不了的,究其根源,乃是Java設計之初偷懶所致。正常狀況下,聲明某個對象時理應爲其分配默認值,從而確保該對象在任什麼時候候都是有值的,但早期的Java圖省事,若是程序員沒在聲明對象的同時加以賦值,那麼系統也不給它初始化,結果該對象只好指向一個虛無縹緲的空間,而在太虛幻境中不管作什麼事情都只能是黃粱一夢。
空指針的設計缺陷根深蒂固,以致於後來的Java版本難以根除該毛病,遲至Java8才推出了針對空指針的解決方案——可選器Optional。Optional本質上是一種特殊的容器,其內部有且僅有一個元素,同時該元素還可能爲空。圍繞着這個可空元素,Optional衍生出若干泛型方法,目的是將複雜的流程控制語句概括爲接續進行的方法調用。爲了兼容已有的Java代碼,一般並不直接構造Optional實例,而是調用它的ofNullable方法塞入某個實體對象,再調用Optional實例的其它方法進行處理。Optional經常使用的實例方法羅列以下:
get:獲取可選器中保存的元素。若是元素爲空,則扔出無此元素異常NoSuchElementException。
isPresent:判斷可選器中元素是否爲空。非空返回true,爲空返回false。
ifPresent:若是元素非空,則對該元素執行指定的Consumer消費事件。
filter:若是元素非空,則根據Predicate斷言條件檢查該元素是否符合要求,只有符合才原樣返回,若不符合則返回空值。
map:若是元素非空,則執行Function函數實例規定的操做,並返回指定格式的數據。
orElse:若是元素非空就返回該元素,不然返回指定的對象值。
orElseThrow:若是元素非空就返回該元素,不然扔出指定的異常。
接下來看一個Optional的簡單應用例子,以前在蘋果類中寫了isRedApple方法,用來判斷自身是否爲紅蘋果,該方法的代碼以下所示:app
// 判斷是否紅蘋果 public boolean isRedApple() { // 不嚴謹的寫法。一旦color字段爲空,就會發生空指針異常 return this.color.toLowerCase().equals("red"); }
顯而易見這個isRedApple方法很不嚴謹,一旦顏色color字段爲空,就會發生空指針異常。常規的補救天然是增長空指針判斷,遇到空指針的狀況便自動返回false,此時方法代碼優化以下:函數
// 判斷是否紅蘋果 public boolean isRedApple() { // 常規的寫法,判斷color字段是否爲空,再作分支處理 boolean isRed = (this.color==null) ? false : this.color.toLowerCase().equals("red"); return isRed; }
如今藉助可空器Optional,支持一路過來的方法調用,先調用ofNullable方法設置對象實例,再調用map方法轉換數據類型,再調用orElse方法設置空指針之時的取值,最後調用equals方法進行顏色對比。採起Optional形式的方法代碼示例以下:優化
// 判斷是否紅蘋果 public boolean isRedApple() { // 利用Optional進行可空對象的處理,可空對象指的是該對象可能不存在(空指針) boolean isRed = Optional.ofNullable(this.color) // 構造一個可空對象 .map(color -> color.toLowerCase()) // map指定了非空時候的取值 .orElse("null") // orElse設置了空指針時候的取值 .equals("red"); // 再判斷是否紅蘋果 return isRed; }
然而上面Optional方式的代碼行數明顯超過了條件分支語句,它的先進性又何從體現呢?其實可選器並不是要徹底取代原先的空指針判斷,而是提供了另外一種解決問題的新思路,經過合理搭配各項技術,方能取得最優的解決辦法。仍以挑選紅蘋果爲例,本來判斷元素非空的分支語句「if (item != null)」,採用Optional改進以後的循環代碼以下所示:this
// 把for循環的內部代碼改寫爲Optional校驗方式 private static void getRedAppleWithOptionalOne(List<Apple> list) { List<Apple> redAppleList = new ArrayList<Apple>(); if (list != null) { // 判斷清單非空 for (Apple item : list) { // 遍歷現有的蘋果清單 if (Optional.ofNullable(item) // 構造一個可空對象 .map(apple -> apple.isRedApple()) // map指定了item非空時候的取值 .orElse(false)) { // orElse指定了item爲空時候的取值 redAppleList.add(item); } } } System.out.println("Optional1判斷 紅蘋果清單=" + redAppleList.toString()); }
注意到以上代碼仍然存在形如「if (list != null)」的清單非空判斷,並且該分支後面還有要命的for循環,這下既要利用Optional的ifPresent方法輸入消費行爲,又要使用流式處理的forEach方法遍歷每一個元素。因而進一步改寫後的Optional代碼變成了下面這般:編碼
// 把清單的非空判斷代碼改寫爲Optional校驗方式 private static void getRedAppleWithOptionalTwo(List<Apple> list) { List<Apple> redAppleList = new ArrayList<Apple>(); Optional.ofNullable(list) // 構造一個可空對象 .ifPresent( // ifPresent指定了list非空時候的處理 apples -> { apples.stream().forEach( // 對蘋果清單進行流式處理 item -> { if (Optional.ofNullable(item) // 構造一個可空對象 .map(apple -> apple.isRedApple()) // map指定了item非空時候的取值 .orElse(false)) { // orElse指定了item爲空時候的取值 redAppleList.add(item); } }); }); System.out.println("Optional2判斷 紅蘋果清單=" + redAppleList.toString()); }
雖然二度改進後的代碼已經消除了空指針判斷分支,可是依然留下是否爲紅蘋果的校驗分支,僅存的if語句着實礙眼,乾脆一不作二不休引入流式處理的filter方法替換if語句。幾經修改獲得瞭如下的最終優化代碼:設計
// 聯合運用Optional校驗和流式處理 private static void getRedAppleWithOptionalThree(List<Apple> list) { List<Apple> redAppleList = new ArrayList<Apple>(); Optional.ofNullable(list) // 構造一個可空對象 .ifPresent(apples -> { // ifPresent指定了list非空時候的處理 // 從原始清單中篩選出紅蘋果清單 redAppleList.addAll(apples.stream() .filter(a -> a != null) // 只挑選非空元素 .filter(Apple::isRedApple) // 只挑選紅蘋果 .collect(Collectors.toList())); // 返回結果清單 }); System.out.println("Optional3判斷 紅蘋果清單=" + redAppleList.toString()); }
好不容易去掉了全部if和for語句,儘管代碼的總行數未有明顯減小,不過邏輯結構顯然變得更加清晰了。指針
更多Java技術文章參見《Java開發筆記(序)章節目錄》