Java函數式開辟 Optional空指針處置。本站提示廣大學習愛好者:(Java函數式開辟 Optional空指針處置)文章只能為提供參考,不一定能成為您想要的結果。以下是Java函數式開辟 Optional空指針處置正文
摘要
余暇時會抽閒進修同在jvm上運轉的Groovy和Scala,發明他們對null的處置比晚期版本Java鄭重許多。在Java8中,Optional為函數式編程的null處置給出了異常優雅的處理計劃。本文將解釋久長以來Java中對null的糟糕處置,然後引見應用Optional來完成Java函數式編程。
那些年困擾著我們的null
在Java江湖傳播著如許一個傳說:直到真正懂得了空指針異常,能力算一位及格的Java開辟人員。在我們逼格閃閃的java碼字符生活中,天天都邑碰到各類null的處置,像上面如許的代碼能夠我們天天都在重復編寫:
if(null != obj1){ if(null != obje2){ // do something } }
略微有點眼界javaer就去干一些稍有逼格的事,弄一個斷定null的辦法:
boolean checkNotNull(Object obj){ return null == obj ? false : true; } void do(){ if(checkNotNull(obj1)){ if(checkNotNull(obj2)){ //do something } } }
然後,成績又來了:假如一個null表現一個空字符串,那”"表現甚麼?
然後慣性思想告知我們,”"和null不都是空字符串碼?索性就把斷定空值進級了一下:
boolean checkNotBlank(Object obj){ return null != obj && !"".equals(obj) ? true : false; } void do(){ if(checkNotBlank(obj1)){ if(checkNotNull(obj2)){ //do something } } }
有空的話列位可以看看今朝項目中或許本身過往的代碼,究竟寫了若干和下面相似的代碼。
不曉得你能否賣力思慮過一個成績:一個null究竟意味著甚麼?
回想一下,在我們後面碼字生活中究竟碰到過量少次java.lang.NullPointerException異常?NullPointerException作為一個RuntimeException級其余異常不消顯示捕捉,若不當心處置我們常常會在臨盆日記中看到各類由NullPointerException惹起的異常客棧輸入。並且依據這個異常客棧信息我們基本沒法定位到招致成績的緣由,由於其實不是拋出NullPointerException的處所激發了這個成績。我們得更深處去查詢甚麼處所發生了這個null,而這個時刻日記常常沒法跟蹤。
有時更喜劇的是,發生null值的處所常常不在我們本身的項目代碼中。這就存在一個更為難的現實——在我們挪用各類良莠不齊第三方接口時,說不清某個接口在某種機緣偶合的情形下就會前往一個null……
回到後面對null的認知成績。許多javaer以為null就是表現“甚麼都沒有”或許“值不存在”。依照這個慣性思想我們的代碼邏輯就是:你挪用我的接口,依照你給我的參數前往對應的“值”,假如這前提沒法找到對應的“值”,那我固然前往一個null給你表現沒有“任何器械”了。我們看看上面這個代碼,用很傳統很尺度的Java編碼作風編寫:
class MyEntity{ int id; String name; String getName(){ return name; } } // main public class Test{ public static void main(String[] args) final MyEntity myEntity = getMyEntity(false); System.out.println(myEntity.getName()); } private getMyEntity(boolean isSuc){ if(isSuc){ return new MyEntity(); }else{ return null; } } }
這一段代碼很簡略,平常的營業代碼確定比這個龐雜的多,然則現實上我們年夜量的Java編碼都是按這類套路編寫的,懂貨的人一眼便可以看出終究確定會拋出NullPointerException。然則在我們編寫營業代碼時,很少會想到要處置這個能夠會湧現的null(或許API文檔曾經寫得很清晰在某些情形下會前往null,然則你確保你會賣力看完API文檔後才開端寫代碼麼?),直到我們到了某個測試階段,忽然蹦出一個NullPointerException異常,我們才認識到本來我們得像上面如許加一個斷定來弄定這個能夠會前往的null值。
// main public class Test{ public static void main(String[] args) final MyEntity myEntity = getMyEntity(false); if(null != myEntity){ System.out.println(myEntity.getName()); }else{ System.out.println("ERROR"); } } }
細心想一想曩昔這麼些年,我們是否是都如許干過去的?假如直到測試階段能力發明某些null招致的成績,那末如今成績就來了——在那些雍容復雜、條理清楚的營業代碼中究竟還有若干null沒有被准確處置呢?
關於null的處置立場,常常可以看出一個項目標成熟和嚴謹水平。好比Guava早在JDK1.6之前就給出了優雅的null處置方法,可見功底之深。
鬼怪普通的null障礙我們提高
假如你是一名聚焦於傳統面向對象開辟的Javaer,也許你曾經習氣了null帶來的各種成績。然則早在很多年前,年夜神就說了null這玩意就是個坑。
托尼.霍爾(你不曉得這貨是誰嗎?本身去查查吧)已經說過:“I call it my billion-dollar mistake. It was the invention of the null reference in 1965. I couldn't resist the temptation to put in a null reference, simply because it was so easy to implement.”(年夜意是:“哥將創造null這事稱為無價之寶的毛病。由於在1965誰人盤算機的蠻荒時期,空援用太輕易完成,讓哥基本經不住引誘創造了空指針這玩意。”)。
然後,我們再看看null還會引入甚麼成績。
看看上面這個代碼:
String address = person.getCountry().getProvince().getCity();
假如你玩過一些函數式說話(Haskell、Erlang、Clojure、Scala等等),下面如許是一種很天然的寫法。用Java固然也能夠完成下面如許的編寫方法。
然則為了美滿的處置一切能夠湧現的null異常,我們不能不把這類優雅的函數編程范式改成如許:
if (person != null) { Country country = person.getCountry(); if (country != null) { Province province = country.getProvince(); if (province != null) { address = province.getCity(); } } }
剎時,高逼格的函數式編程Java8又回到了10年前。如許一層一層的嵌套斷定,增長代碼量和不優雅照樣大事。更能夠湧現的情形是:在年夜部門時光裡,人們會忘卻去斷定這能夠會湧現的null,即便是寫了多年月碼的白叟家也不破例。
下面這一段層層嵌套的 null 處置,也是傳統Java歷久被诟病的處所。假如以Java晚期版本作為你的發蒙說話,這類get->if null->return 的臭缺點會影響你很長的時光(記得在某國外社區,這被稱為:面向entity開辟)。
應用Optional完成Java函數式編程
好了,說了各類各樣的缺點,然後我們可以進入新時期了。
早在推出Java SE 8版本之前,其他相似的函數式開辟說話早就有本身的各類處理計劃。上面是Groovy的代碼:
String version = computer?.getSoundcard()?.getUSB()?.getVersion():"unkonwn";
Haskell用一個 Maybe 類型類標識處置null值。而號稱多范式開辟說話的Scala則供給了一個和Maybe差不多意思的Option[T],用來包裹處置null。
Java8引入了 java.util.Optional<T>來處置函數式編程的null成績,Optional<T>的處置思緒和Haskell、Scala相似,但又有些許差別。先看看上面這個Java代碼的例子:
public class Test { public static void main(String[] args) { final String text = "Hallo world!"; Optional.ofNullable(text)//顯示創立一個Optional殼 .map(Test::print) .map(Test::print) .ifPresent(System.out::println); Optional.ofNullable(text) .map(s ->{ System.out.println(s); return s.substring(6); }) .map(s -> null)//前往 null .ifPresent(System.out::println); } // 打印並截取str[5]以後的字符串 private static String print(String str) { System.out.println(str); return str.substring(6); } } //Consol 輸入 //num1:Hallo world! //num2:world! //num3: //num4:Hallo world!
(可以把下面的代碼copy到你的IDE中運轉,條件是必需裝置了JDK8。)
下面的代碼中創立了2個Optional,完成的功效根本雷同,都是應用Optional作為String的外殼對String停止截斷處置。當在處置進程中碰到null值時,就不再持續處置。我們可以發明第二個Optional中湧現s->null以後,後續的ifPresent不再履行。
留意不雅察輸入的 //num3:,這表現輸入了一個”"字符,而不是一個null。
Optional供給了豐碩的接口來處置各類情形,好比可以將代碼修正為:
public class Test { public static void main(String[] args) { final String text = "Hallo World!"; System.out.println(lowerCase(text));//辦法一 lowerCase(null, System.out::println);//辦法二 } private static String lowerCase(String str) { return Optional.ofNullable(str).map(s -> s.toLowerCase()).map(s->s.replace("world", "java")).orElse("NaN"); } private static void lowerCase(String str, Consumer<String> consumer) { consumer.accept(lowerCase(str)); } } //輸入 //hallo java! //NaN
如許,我們可以靜態的處置一個字符串,假如在任什麼時候候發明值為null,則應用orElse前往預設默許的“NaN”。
總的來講,我們可以將任何數據構造用Optional包裹起來,然後應用函數式的方法對他停止處置,而不用關懷隨時能夠會湧現的null。
我們看看後面提到的Person.getCountry().getProvince().getCity()怎樣不消一堆if來處置。
第一種辦法是不轉變之前的entity:
import java.util.Optional; public class Test { public static void main(String[] args) { System.out.println(Optional.ofNullable(new Person()) .map(x->x.country) .map(x->x.provinec) .map(x->x.city) .map(x->x.name) .orElse("unkonwn")); } } class Person { Country country; } class Country { Province provinec; } class Province { City city; } class City { String name; }
這裡用Optional作為每次前往的外殼,假如有某個地位前往了null,則會直接獲得”unkonwn”。
第二種方法是將一切的值都用Optional來界說:
import java.util.Optional; public class Test { public static void main(String[] args) { System.out.println(new Person() .country.flatMap(x -> x.provinec) .flatMap(Province::getCity) .flatMap(x -> x.name) .orElse("unkonwn")); } } class Person { Optional<Country> country = Optional.empty(); } class Country { Optional<Province> provinec; } class Province { Optional<City> city; Optional<City> getCity(){//用於:: return city; } } class City { Optional<String> name; }
第一種辦法可以膩滑的和已有的JavaBean、Entity或POJA整合,而無需修改甚麼,也能更輕松的整合到第三方接口中(例如spring的bean)。建議今朝照樣以第一種Optional的應用辦法為主,究竟不是團隊中每小我都能懂得每一個get/set帶著一個Optional的意圖。
Optional還供給了一個filter辦法用於過濾數據(現實上Java8裡stream作風的接口都供給了filter辦法)。例如曩昔我們斷定值存在並作出響應的處置:
if(Province!= null){ City city = Province.getCity(); if(null != city && "guangzhou".equals(city.getName()){ System.out.println(city.getName()); }else{ System.out.println("unkonwn"); } }
如今我們可以修正為
Optional.ofNullable(province) .map(x->x.city) .filter(x->"guangzhou".equals(x.getName())) .map(x->x.name) .orElse("unkonw");
到此,應用Optional來停止函數式編程引見終了。Optional除下面提到的辦法,還有orElseGet、orElseThrow等依據更多須要供給的辦法。orElseGet會由於湧現null值拋出空指針異常,而orElseThrow會在湧現null時,拋出一個應用者自界說的異常。可以檢查API文檔來懂得一切辦法的細節。
寫在最初的
Optional只是Java函數式編程的冰山一角,須要聯合lambda、stream、Funcationinterface等特征能力真實的懂得Java8函數式編程的功效。原來還想引見一些Optional的源碼和運轉道理的,然則Optional自己的代碼就很少、API接口也不多,細心想一想也沒甚麼好說的就省略了。
Optional固然優雅,然則小我感到有一些效力成績,不外還沒去驗證。假如有誰有確切的數據,請告知我。
自己也不是“函數式編程支撐者”。從團隊治理者的角度來講,每晉升一點進修難度,人員的應用本錢和團隊交互本錢就會更高一些。就像在傳說中Lisp可以比C++的代碼量少三十倍、開辟更高效,然則若一個國際的慣例IT公司真用Lisp來做項目,請問去哪、得花若干錢弄到這些用Lisp的哥們啊?
然則我異常勉勵年夜家都進修和懂得函數式編程的思緒。特別是曩昔只侵淫在Java這一門說話、到如今還不清晰Java8會帶來甚麼轉變的開辟人員,Java8是一個優越的契機。更勉勵把新的Java8特征引入到今朝的項目中,一個歷久合營的團隊和一門陳舊的編程說話都須要赓續的注入新活氣,不然不進則退。
以上就是對Java Optional 的材料整頓,後續持續彌補相干材料,感謝年夜家對本站的支撐!