Java 8的Optional的正確打開方式

本文在理清Optional的基本用法的同時,附帶對比Optional中的map/flatMap和stream中的map/flatMap。

問題

Java 8中的Optional號稱可以避免NPE(Null PointerException) 。但在日常工作中看到很多代碼中Optional的使用,是用 isPresent() 函數(shù)來判別是否為null。下面這樣的判斷方式,還不如直接 bean !=null 這樣的判斷,區(qū)別只是沒有出現(xiàn)null這個關(guān)鍵詞而已。更不用說鏈式的,不停的調(diào)用isPresent()。
如下:

if(beanOpt.isPresent()){
  do somethings..
}

if(beanOpt.isPresent() && beanOpt.get().getBean1().isPresent()){
  beanOpt.get().getBean1().get() ... do somethings
}

這樣的代碼讓閱讀的人不想讀,修改的人不愿意修改。

這是起到了規(guī)避Null判斷的作用,但是多此一舉,顯然,也不是Optional的正確用法。避免NPE是要Optional配合map / flatmap函數(shù)使用的。

map / flatMap的基本含義

map/flatMap在函數(shù)式編程中非常常用(map與reduce組成經(jīng)典的map-reduce編程范式,flatMap是map的一種語法糖)。通常map是做元素映射,flatMap是做映射的同時去掉多層級的集合,只保留一層(所謂flat——打平)。Optional也延用了這兩個名稱,基本含義保持,特別的是Optional中僅一個元素,打平是去optional的包裝,只保留一層。

Optional的map, flatmap的用法

  • map / flatmap 函數(shù)
  1. map/flatMap的函數(shù)參數(shù)中,函數(shù)的輸入是optional內(nèi)被包裝的對象,因此不需要顯式處理optional的拆箱操作(用拆箱表達從optional中g(shù)et元素的動作)
  2. map/flatMap的返回值會被自動包裝到optional內(nèi)返回,可以使用orElse(),ifPresent()來避免if-else的null判斷
  3. 因為map/flatMap的這個自動拆optional的這個特性,可以對于多層optional嵌套可以鏈式map搞定
  4. 這個自動拆箱的過程不會拋出NPE,為null的元素會作為Optional.empty()向后傳遞
  5. map/flatMap的區(qū)別在于,如果函數(shù)參數(shù)中的返回值為optional,flatMap只會留一層option包裝,而map會直接保留原有optional的包裝(見第二段代碼示例),因此flatMap的函數(shù)參數(shù)中的返回值必須是optional包裝對象(這個限制在函數(shù)聲明中已經(jīng)限制)

這些就是optional為處理NPE的工作。除此外optional還有很多方法,都非常簡潔表意,不再贅述。

// map 處理NPE
    public static void main(String[] args) {
        Optional<String> strOpt = Optional.of("hello world!");
        Optional<String> emptyOpt = Optional.empty();
        
        System.out.println(getStrLength(strOpt));
        System.out.println(getStrLength(emptyOpt));
    }

    private static Integer getStrLength(Optional<String> strOpt) {
        return strOpt.map(String::length).orElse(0);
    }

--輸出------
12
0

Process finished with exit code 0
// map / flatMap 的對比

    public static void main(String[] args) {
        Optional<Bean> beanOpt = Optional.of(new Bean("beanId"));
        Optional<Bean> emptyOpt = Optional.empty();

        System.out.println(testFlatMap(beanOpt));
        System.out.println(testFlatMap(emptyOpt));

        System.out.println(testMap(beanOpt));
        System.out.println(testMap(emptyOpt));
    }

    private static String testFlatMap(Optional<Bean> strOpt) {
        return strOpt.flatMap(Bean::getId).orElse("noneId");
    }


    private static Optional<String> testMap(Optional<Bean> strOpt) {
        return strOpt.map(Bean::getId).orElse(Optional.of("noneId"));
    }

    static class Bean{
        String id;

        public Bean(String id) {
            this.id = id;
        }

        public Optional<String> getId() {
            return Optional.ofNullable(id);
        }

        public void setId(String id) {
            this.id = id;
        }
    }

---輸出--------
beanId
noneId
Optional[beanId]
Optional[noneId]

Process finished with exit code 0

延展:函數(shù)式中的map, flatmap

  • map的基本用法
    public static void main(String[] args) {
        List<Bean> beans = new ArrayList<>();
        beans.add(new Bean("bean1"));
        beans.add(new Bean("bean2"));
        beans.add(new Bean());

        // 對每個beans中的bean,獲取他們的id字段,轉(zhuǎn)換為對應(yīng)的集合
        
        List<String> ids = beans.stream()
                .map(bean -> bean.getId())
                .map(opt -> opt.orElse("noneId"))
                .collect(Collectors.toList());

        System.out.println(ids.stream().collect(Collectors.joining(",")));
    }

    static class Bean{
        String id;

        public Bean(String id) {
            this.id = id;
        }
        public Bean() {
        }

        public Optional<String> getId() {
            return Optional.ofNullable(id);
        }

        public void setId(String id) {
            this.id = id;
        }
    }
---- 輸出-------------------
bean1,bean2,noneId

Process finished with exit code 0

  • flatMap的基本用法


    public static void main(String[] args) {
        Stream<Bean> beanStream = Stream.of(
                new Bean("bean1"), 
                new Bean("bean2"), 
                new Bean());

        // 對每個beans中的bean,獲取他們的id字段,轉(zhuǎn)換為對應(yīng)的集合
        List<String> chars =  beanStream
                .map(bean -> bean.getId().orElse("noneId"))
                .flatMap(id -> Arrays.stream(id.split("")))
                .collect(Collectors.toList());
        System.out.println(chars.stream().collect(Collectors.joining(",")));
    }

    static class Bean{
        String id;

        public Bean(String id) {
            this.id = id;
        }
        public Bean() {
        }

        public Optional<String> getId() {
            return Optional.ofNullable(id);
        }

        public void setId(String id) {
            this.id = id;
        }
    }

-----輸出----------------
b,e,a,n,1,b,e,a,n,2,n,o,n,e,I,d

Process finished with exit code 0
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

友情鏈接更多精彩內(nèi)容