欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

Java函數(shù)式開(kāi)發(fā) Optional空指針處理

 更新時(shí)間:2016年09月04日 17:16:39   作者:隨風(fēng)溜達(dá)的向日葵  
本文主要介紹Java函數(shù)式開(kāi)發(fā) Optional空指針處理,這里整理了相關(guān)資料,及示例代碼,有興趣的小伙伴可以參考下

摘要

空閑時(shí)會(huì)抽空學(xué)習(xí)同在jvm上運(yùn)行的Groovy和Scala,發(fā)現(xiàn)他們對(duì)null的處理比早期版本Java慎重很多。在Java8中,Optional為函數(shù)式編程的null處理給出了非常優(yōu)雅的解決方案。本文將說(shuō)明長(zhǎng)久以來(lái)Java中對(duì)null的蹩腳處理,然后介紹使用Optional來(lái)實(shí)現(xiàn)Java函數(shù)式編程。

那些年困擾著我們的null

在Java江湖流傳著這樣一個(gè)傳說(shuō):直到真正了解了空指針異常,才能算一名合格的Java開(kāi)發(fā)人員。在我們逼格閃閃的java碼字符生涯中,每天都會(huì)遇到各種null的處理,像下面這樣的代碼可能我們每天都在反復(fù)編寫:

if(null != obj1){
 if(null != obje2){
   // do something
 }
}

稍微有點(diǎn)眼界javaer就去干一些稍有逼格的事,弄一個(gè)判斷null的方法:

boolean checkNotNull(Object obj){
 return null == obj ? false : true; 
}

void do(){
 if(checkNotNull(obj1)){
   if(checkNotNull(obj2)){
    //do something
   }
 }
}

然后,問(wèn)題又來(lái)了:如果一個(gè)null表示一個(gè)空字符串,那”"表示什么?

然后慣性思維告訴我們,”"和null不都是空字符串碼?索性就把判斷空值升級(jí)了一下:

boolean checkNotBlank(Object obj){
 return null != obj && !"".equals(obj) ? true : false; 
}
void do(){
 if(checkNotBlank(obj1)){
   if(checkNotNull(obj2)){
    //do something
   }
 }
}

有空的話各位可以看看目前項(xiàng)目中或者自己過(guò)往的代碼,到底寫了多少和上面類似的代碼。

不知道你是否認(rèn)真思考過(guò)一個(gè)問(wèn)題:一個(gè)null到底意味著什么?

  1. 淺顯的認(rèn)識(shí)——null當(dāng)然表示“值不存在”。
  2. 對(duì)內(nèi)存管理有點(diǎn)經(jīng)驗(yàn)的理解——null表示內(nèi)存沒(méi)有被分配,指針指向了一個(gè)空地址。
  3. 稍微透徹點(diǎn)的認(rèn)識(shí)——null可能表示某個(gè)地方處理有問(wèn)題了,也可能表示某個(gè)值不存在。
  4. 被虐千萬(wàn)次的認(rèn)識(shí)——哎喲,又一個(gè)NullPointerException異常,看來(lái)我得加一個(gè)if(null != value)了。

回憶一下,在咱們前面碼字生涯中到底遇到過(guò)多少次java.lang.NullPointerException異常?NullPointerException作為一個(gè)RuntimeException級(jí)別的異常不用顯示捕獲,若不小心處理我們經(jīng)常會(huì)在生產(chǎn)日志中看到各種由NullPointerException引起的異常堆棧輸出。而且根據(jù)這個(gè)異常堆棧信息我們根本無(wú)法定位到導(dǎo)致問(wèn)題的原因,因?yàn)椴⒉皇菕伋鯪ullPointerException的地方引發(fā)了這個(gè)問(wèn)題。我們得更深處去查詢什么地方產(chǎn)生了這個(gè)null,而這個(gè)時(shí)候日志往往無(wú)法跟蹤。

有時(shí)更悲劇的是,產(chǎn)生null值的地方往往不在我們自己的項(xiàng)目代碼中。這就存在一個(gè)更尷尬的事實(shí)——在我們調(diào)用各種良莠不齊第三方接口時(shí),說(shuō)不清某個(gè)接口在某種機(jī)緣巧合的情況下就會(huì)返回一個(gè)null……

回到前面對(duì)null的認(rèn)知問(wèn)題。很多javaer認(rèn)為null就是表示“什么都沒(méi)有”或者“值不存在”。按照這個(gè)慣性思維我們的代碼邏輯就是:你調(diào)用我的接口,按照你給我的參數(shù)返回對(duì)應(yīng)的“值”,如果這條件沒(méi)法找到對(duì)應(yīng)的“值”,那我當(dāng)然返回一個(gè)null給你表示沒(méi)有“任何東西”了。我們看看下面這個(gè)代碼,用很傳統(tǒng)很標(biāo)準(zhǔn)的Java編碼風(fēng)格編寫:

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;
    }
  }
}

這一段代碼很簡(jiǎn)單,日常的業(yè)務(wù)代碼肯定比這個(gè)復(fù)雜的多,但是實(shí)際上我們大量的Java編碼都是按這種套路編寫的,懂貨的人一眼就可以看出最終肯定會(huì)拋出NullPointerException。但是在我們編寫業(yè)務(wù)代碼時(shí),很少會(huì)想到要處理這個(gè)可能會(huì)出現(xiàn)的null(也許API文檔已經(jīng)寫得很清楚在某些情況下會(huì)返回null,但是你確保你會(huì)認(rèn)真看完API文檔后才開(kāi)始寫代碼么?),直到我們到了某個(gè)測(cè)試階段,突然蹦出一個(gè)NullPointerException異常,我們才意識(shí)到原來(lái)我們得像下面這樣加一個(gè)判斷來(lái)搞定這個(gè)可能會(huì)返回的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");
    }
  }
}

仔細(xì)想想過(guò)去這么些年,咱們是不是都這樣干過(guò)來(lái)的?如果直到測(cè)試階段才能發(fā)現(xiàn)某些null導(dǎo)致的問(wèn)題,那么現(xiàn)在問(wèn)題就來(lái)了——在那些雍容繁雜、層次分明的業(yè)務(wù)代碼中到底還有多少null沒(méi)有被正確處理呢?

對(duì)于null的處理態(tài)度,往往可以看出一個(gè)項(xiàng)目的成熟和嚴(yán)謹(jǐn)程度。比如Guava早在JDK1.6之前就給出了優(yōu)雅的null處理方式,可見(jiàn)功底之深。

鬼魅一般的null阻礙我們進(jìn)步

如果你是一位聚焦于傳統(tǒng)面向?qū)ο箝_(kāi)發(fā)的Javaer,或許你已經(jīng)習(xí)慣了null帶來(lái)的種種問(wèn)題。但是早在許多年前,大神就說(shuō)了null這玩意就是個(gè)坑。

托尼.霍爾(你不知道這貨是誰(shuí)嗎?自己去查查吧)曾經(jīng)說(shuō)過(guò):“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.”(大意是:“哥將發(fā)明null這事稱為價(jià)值連城的錯(cuò)誤。因?yàn)樵?965那個(gè)計(jì)算機(jī)的蠻荒時(shí)代,空引用太容易實(shí)現(xiàn),讓哥根本經(jīng)不住誘惑發(fā)明了空指針這玩意。”)。

然后,我們?cè)倏纯磏ull還會(huì)引入什么問(wèn)題。

看看下面這個(gè)代碼:

String address = person.getCountry().getProvince().getCity();

如果你玩過(guò)一些函數(shù)式語(yǔ)言(Haskell、Erlang、Clojure、Scala等等),上面這樣是一種很自然的寫法。用Java當(dāng)然也可以實(shí)現(xiàn)上面這樣的編寫方式。

但是為了完滿的處理所有可能出現(xiàn)的null異常,我們不得不把這種優(yōu)雅的函數(shù)編程范式改為這樣:

if (person != null) {
 Country country = person.getCountry();
 if (country != null) {
 Province province = country.getProvince();
 if (province != null) {
  address = province.getCity();
 }
 }
}

瞬間,高逼格的函數(shù)式編程Java8又回到了10年前。這樣一層一層的嵌套判斷,增加代碼量和不優(yōu)雅還是小事。更可能出現(xiàn)的情況是:在大部分時(shí)間里,人們會(huì)忘記去判斷這可能會(huì)出現(xiàn)的null,即使是寫了多年代碼的老人家也不例外。

上面這一段層層嵌套的 null 處理,也是傳統(tǒng)Java長(zhǎng)期被詬病的地方。如果以Java早期版本作為你的啟蒙語(yǔ)言,這種get->if null->return 的臭毛病會(huì)影響你很長(zhǎng)的時(shí)間(記得在某國(guó)外社區(qū),這被稱為:面向entity開(kāi)發(fā))。

利用Optional實(shí)現(xiàn)Java函數(shù)式編程

好了,說(shuō)了各種各樣的毛病,然后我們可以進(jìn)入新時(shí)代了。

早在推出Java SE 8版本之前,其他類似的函數(shù)式開(kāi)發(fā)語(yǔ)言早就有自己的各種解決方案。下面是Groovy的代碼:

String version = computer?.getSoundcard()?.getUSB()?.getVersion():"unkonwn";

Haskell用一個(gè) Maybe 類型類標(biāo)識(shí)處理null值。而號(hào)稱多范式開(kāi)發(fā)語(yǔ)言的Scala則提供了一個(gè)和Maybe差不多意思的Option[T],用來(lái)包裹處理null。

Java8引入了 java.util.Optional<T>來(lái)處理函數(shù)式編程的null問(wèn)題,Optional<T>的處理思路和Haskell、Scala類似,但又有些許區(qū)別。先看看下面這個(gè)Java代碼的例子:

public class Test {
 public static void main(String[] args) {
 final String text = "Hallo world!";
 Optional.ofNullable(text)//顯示創(chuàng)建一個(gè)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中運(yùn)行,前提是必須安裝了JDK8。)

上面的代碼中創(chuàng)建了2個(gè)Optional,實(shí)現(xiàn)的功能基本相同,都是使用Optional作為String的外殼對(duì)String進(jìn)行截?cái)嗵幚?。?dāng)在處理過(guò)程中遇到null值時(shí),就不再繼續(xù)處理。我們可以發(fā)現(xiàn)第二個(gè)Optional中出現(xiàn)s->null之后,后續(xù)的ifPresent不再執(zhí)行。

注意觀察輸出的 //num3:,這表示輸出了一個(gè)”"字符,而不是一個(gè)null。

Optional提供了豐富的接口來(lái)處理各種情況,比如可以將代碼修改為:

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

這樣,我們可以動(dòng)態(tài)的處理一個(gè)字符串,如果在任何時(shí)候發(fā)現(xiàn)值為null,則使用orElse返回預(yù)設(shè)默認(rèn)的“NaN”。

總的來(lái)說(shuō),我們可以將任何數(shù)據(jù)結(jié)構(gòu)用Optional包裹起來(lái),然后使用函數(shù)式的方式對(duì)他進(jìn)行處理,而不必關(guān)心隨時(shí)可能會(huì)出現(xiàn)的null。

我們看看前面提到的Person.getCountry().getProvince().getCity()怎么不用一堆if來(lái)處理。

第一種方法是不改變以前的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作為每一次返回的外殼,如果有某個(gè)位置返回了null,則會(huì)直接得到”unkonwn”。

第二種辦法是將所有的值都用Optional來(lái)定義:

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整合,而無(wú)需改動(dòng)什么,也能更輕松的整合到第三方接口中(例如spring的bean)。建議目前還是以第一種Optional的使用方法為主,畢竟不是團(tuán)隊(duì)中每一個(gè)人都能理解每個(gè)get/set帶著一個(gè)Optional的用意。

Optional還提供了一個(gè)filter方法用于過(guò)濾數(shù)據(jù)(實(shí)際上Java8里stream風(fēng)格的接口都提供了filter方法)。例如過(guò)去我們判斷值存在并作出相應(yīng)的處理:

if(Province!= null){
 City city = Province.getCity();
 if(null != city && "guangzhou".equals(city.getName()){
  System.out.println(city.getName());
 }else{
  System.out.println("unkonwn");
 }
}

現(xiàn)在我們可以修改為

Optional.ofNullable(province)
  .map(x->x.city)
  .filter(x->"guangzhou".equals(x.getName()))
  .map(x->x.name)
  .orElse("unkonw");

到此,利用Optional來(lái)進(jìn)行函數(shù)式編程介紹完畢。Optional除了上面提到的方法,還有orElseGet、orElseThrow等根據(jù)更多需要提供的方法。orElseGet會(huì)因?yàn)槌霈F(xiàn)null值拋出空指針異常,而orElseThrow會(huì)在出現(xiàn)null時(shí),拋出一個(gè)使用者自定義的異常??梢圆榭碅PI文檔來(lái)了解所有方法的細(xì)節(jié)。

寫在最后的

Optional只是Java函數(shù)式編程的冰山一角,需要結(jié)合lambda、stream、Funcationinterface等特性才能真正的了解Java8函數(shù)式編程的效用。本來(lái)還想介紹一些Optional的源碼和運(yùn)行原理的,但是Optional本身的代碼就很少、API接口也不多,仔細(xì)想想也沒(méi)什么好說(shuō)的就省略了。

Optional雖然優(yōu)雅,但是個(gè)人感覺(jué)有一些效率問(wèn)題,不過(guò)還沒(méi)去驗(yàn)證。如果有誰(shuí)有確實(shí)的數(shù)據(jù),請(qǐng)告訴我。

本人也不是“函數(shù)式編程支持者”。從團(tuán)隊(duì)管理者的角度來(lái)說(shuō),每提升一點(diǎn)學(xué)習(xí)難度,人員的使用成本和團(tuán)隊(duì)交互成本就會(huì)更高一些。就像在傳說(shuō)中Lisp可以比C++的代碼量少三十倍、開(kāi)發(fā)更高效,但是若一個(gè)國(guó)內(nèi)的常規(guī)IT公司真用Lisp來(lái)做項(xiàng)目,請(qǐng)問(wèn)去哪、得花多少錢弄到這些用Lisp的哥們啊?

但是我非常鼓勵(lì)大家都學(xué)習(xí)和了解函數(shù)式編程的思路。尤其是過(guò)去只侵淫在Java這一門語(yǔ)言、到現(xiàn)在還不清楚Java8會(huì)帶來(lái)什么改變的開(kāi)發(fā)人員,Java8是一個(gè)良好的契機(jī)。更鼓勵(lì)把新的Java8特性引入到目前的項(xiàng)目中,一個(gè)長(zhǎng)期配合的團(tuán)隊(duì)以及一門古老的編程語(yǔ)言都需要不斷的注入新活力,否則不進(jìn)則退。

以上就是對(duì)Java Optional 的資料整理,后續(xù)繼續(xù)補(bǔ)充相關(guān)資料,謝謝大家對(duì)本站的支持!

相關(guān)文章

  • 深入理解Java基礎(chǔ)之try-with-resource語(yǔ)法糖

    深入理解Java基礎(chǔ)之try-with-resource語(yǔ)法糖

    這篇文章主要介紹了深入理解Java基礎(chǔ)之try-with-resource語(yǔ)法糖,小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧
    2019-02-02
  • 網(wǎng)關(guān)Spring Cloud Gateway HTTP超時(shí)配置問(wèn)題

    網(wǎng)關(guān)Spring Cloud Gateway HTTP超時(shí)配置問(wèn)題

    這篇文章主要介紹了網(wǎng)關(guān)Spring Cloud Gateway HTTP超時(shí)配置問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2024-01-01
  • hadoop的wordcount實(shí)例代碼

    hadoop的wordcount實(shí)例代碼

    這篇文章主要介紹了hadoop的wordcount實(shí)例代碼,分享了相關(guān)代碼示例,小編覺(jué)得還是挺不錯(cuò)的,具有一定借鑒價(jià)值,需要的朋友可以參考下
    2018-02-02
  • JAVA使用geotools讀取shape格式文件的方法

    JAVA使用geotools讀取shape格式文件的方法

    這篇文章主要介紹了JAVA使用geotools讀取shape格式文件的方法,本文通過(guò)實(shí)例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下
    2017-01-01
  • 淺析SpringBoot統(tǒng)一返回結(jié)果的實(shí)現(xiàn)

    淺析SpringBoot統(tǒng)一返回結(jié)果的實(shí)現(xiàn)

    前后端開(kāi)發(fā)過(guò)程中數(shù)據(jù)交互規(guī)范化是一件非常重要的事情,不僅可以減少前后端交互過(guò)程中出現(xiàn)的問(wèn)題,也讓代碼邏輯更加具有條理,下面小編就和大家講講SpringBoot如何統(tǒng)一返回結(jié)果的吧
    2023-07-07
  • 如何在Spring中自定義scope的方法示例

    如何在Spring中自定義scope的方法示例

    這篇文章主要介紹了如何在Spring中自定義scope的方法示例,小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧
    2019-02-02
  • Java使用sftp定時(shí)下載文件的示例代碼

    Java使用sftp定時(shí)下載文件的示例代碼

    SFTP 為 SSH的其中一部分,是一種傳輸檔案至 Blogger 伺服器的安全方式。接下來(lái)通過(guò)本文給大家介紹了Java使用sftp定時(shí)下載文件的示例代碼,感興趣的朋友跟隨腳本之家小編一起看看吧
    2018-05-05
  • Java下SpringBoot創(chuàng)建定時(shí)任務(wù)詳解

    Java下SpringBoot創(chuàng)建定時(shí)任務(wù)詳解

    這篇文章主要介紹了Java下SpringBoot創(chuàng)建定時(shí)任務(wù)詳解,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2020-07-07
  • SpringCloud之Admin服務(wù)監(jiān)控實(shí)現(xiàn)流程示例詳解

    SpringCloud之Admin服務(wù)監(jiān)控實(shí)現(xiàn)流程示例詳解

    這篇文章主要為大家介紹了SpringCloud之Admin服務(wù)監(jiān)控流程示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2023-09-09
  • IDEA類存在但找不到的解決辦法

    IDEA類存在但找不到的解決辦法

    本文主要介紹了IDEA類存在但找不到的解決辦法,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2022-07-07

最新評(píng)論