1. <strong id="7actg"></strong>
    2. <table id="7actg"></table>

    3. <address id="7actg"></address>
      <address id="7actg"></address>
      1. <object id="7actg"><tt id="7actg"></tt></object>

        Java8 Optional 最佳實(shí)踐

        共 7523字,需瀏覽 16分鐘

         ·

        2021-07-27 02:55

        作者 | ES_her0

        來(lái)源 | https://xie.infoq.cn/article/e3d1f0f4f095397c44812a5be

        很多公眾號(hào)其實(shí)都發(fā)過(guò) Optional 的文章, 但大多文章都是介紹了 Optional 的 API 用法,卻沒(méi)有給出怎么正確的使用 Optional,這可能會(huì)誤導(dǎo)一部分小白使用者,私以為,在項(xiàng)目中一知半解的使用 Optional,我更愿意看到老老實(shí)實(shí)的 null 判斷。今天我給大家分享的這篇文章,便是 Java Optional 的一些 Best Practise 和一些反面的 Bad Practice,以供大家參考。

        來(lái)自作者的說(shuō)明

        首先我們來(lái)看一下Optional的作者 Brian Goetz 對(duì)這個(gè) API 的說(shuō)明:

        Our intention was to provide a limited mechanism for library method return types where there needed to be a clear way to represent "no result", and using null for such was overwhelmingly likely to cause errors.

        大意為,為了避免null帶來(lái)的錯(cuò)誤,我們提供了一個(gè)可以明確表示空值的有限的機(jī)制。

        基礎(chǔ)理解

        首先,Optional是一個(gè)容器,用于放置可能為空的值,它可以合理而優(yōu)雅的處理null。眾所周知,null在編程歷史上極具話(huà)題性,號(hào)稱(chēng)是計(jì)算機(jī)歷史上最嚴(yán)重的錯(cuò)誤,感興趣可以讀一下這篇文章:THE WORST MISTAKE OF COMPUTER SCIENCE,這里暫且不做過(guò)多討論。在 Java 1.8 之前的版本,沒(méi)有可以用于表示null官方 API,如果你足夠的謹(jǐn)慎,你可能需要常常在代碼中做如下的判斷:

        if (null != user) {
            //doing something
        }
        if (StringUtil.isEmpty(string)) {
            //doing something
        }

        確實(shí),返回值是null的情況太多了,一不小心,就會(huì)產(chǎn)生 NPE,接踵而來(lái)的就是應(yīng)用運(yùn)行終止,產(chǎn)品抱怨,用戶(hù)投訴。

        1.8 之后,jdk 新增了Optional來(lái)表示空結(jié)果。其實(shí)本質(zhì)上什么也沒(méi)變,只是增加了一個(gè)表達(dá)方式。Optional表示空的靜態(tài)方法為Optional.empty(),跟null有什么本質(zhì)區(qū)別嗎?其實(shí)沒(méi)有。翻看它的實(shí)現(xiàn),Optional中的 value 就是null,只不過(guò)包了一層Optional,所以說(shuō)它其實(shí)是個(gè)容器。用之后的代碼可能長(zhǎng)這樣:

        // 1
        Optional<User> optionalUser = RemoteService.getUser();
        if (!optionalUser.isPresent()) {
           //doing something 
        }
        User user = optionalUser.get();

        // 2
        User user = optionalUser.get().orElse(new User());

        看起來(lái),好像比之前好了一些,至少看起來(lái)沒(méi)那么笨。但如果采用寫(xiě)法 1,好像更啰嗦了。

        如果你對(duì) kotlin 稍有了解,kotlin 的非空類(lèi)型是他們大肆宣傳的"賣(mài)點(diǎn)"之一,通過(guò)var param!!在使用它的地方做強(qiáng)制的空檢查,否則無(wú)法通過(guò)編譯,最大程度上減少了 NPE。其實(shí)在我看來(lái),Optional的方式更加優(yōu)雅和靈活。同時(shí),Optional也可能會(huì)帶來(lái)一些誤解。

        下面先說(shuō)一些在我看來(lái)不合適的使用方式:

        Bad Practice

        1. 直接使用 isPresent() 進(jìn)行 if 檢查

        這個(gè)直接參考上面的例子,用if判斷和 1.8 之前的寫(xiě)法并沒(méi)有什么區(qū)別,反而返回值包了一層Optional,增加了代碼的復(fù)雜性,沒(méi)有帶來(lái)任何實(shí)質(zhì)的收益。其實(shí)isPresent()一般用于流處理的結(jié)尾,用于判斷是否符合條件。

        list.stream()
            .filer(x -> Objects.equals(x,param))
            .findFirst()
            .isPresent()

        2. 在方法參數(shù)中使用 Optional

        我們用一個(gè)東西之前得想明白,這東西是為解決什么問(wèn)題而誕生的。Optional直白一點(diǎn)說(shuō)就是為了表達(dá)可空性,如果方法參數(shù)可以為空,為何不重載呢?包括使用構(gòu)造函數(shù)也一樣。重載的業(yè)務(wù)表達(dá)更加清晰直觀。

        //don't write method like this
        public void getUser(long uid,Optional<Type> userType);

        //use Overload
        public void getUser(long uid) {
            getUser(uid,null);
        }
        public void getUser(long uid,UserType userType) {
            //doing something
        }

        3. 直接使用 Optional.get

        Optional不會(huì)幫你做任何的空判斷或者異常處理,如果直接在代碼中使用Optional.get()和不做任何空判斷一樣,十分危險(xiǎn)。這種可能會(huì)出現(xiàn)在那種所謂的著急上線(xiàn),著急交付,對(duì)Optional也不是很熟悉,直接就用了。這里多說(shuō)一句,可能有人會(huì)反問(wèn)了:甲方/業(yè)務(wù)著急,需求又多,哪有時(shí)間給他去做優(yōu)化啊?因?yàn)槲以诂F(xiàn)實(shí)工作中遇到過(guò),但這兩者并不矛盾,因?yàn)榇a行數(shù)上差別并不大,只要自己平時(shí)保持學(xué)習(xí),都是信手拈來(lái)的東西。

        4. 使用在 POJO 中

        估計(jì)很少有人這么用:

        public class User {
            private int age;
            private String name;
            private Optional<String> address;
        }

        這樣的寫(xiě)法將會(huì)給序列化帶來(lái)麻煩,Optional本身并沒(méi)有實(shí)現(xiàn)序列化,現(xiàn)有的 JSON 序列化框架也沒(méi)有對(duì)此提供支持的。

        5. 使用在注入的屬性中

        這種寫(xiě)法估計(jì)用的人會(huì)更少,但不排除有腦洞的。

        public class CommonService {
            private Optional<UserService> userService;
            
            public User getUser(String name) {
                return userService.ifPresent(u -> u.findByName(name));
            }
        }

        首先依賴(lài)注入大多在 spring 的框架之下,直接使用@Autowired很方便。但如果使用以上的寫(xiě)法,如果userService set 失敗了,程序就應(yīng)該終止并報(bào)異常,并不是無(wú)聲無(wú)息,讓其看起來(lái)什么問(wèn)題都沒(méi)有。

        Best and Pragmatic Practice

        API

        在說(shuō)最佳實(shí)踐前,讓我們來(lái)看一下Optional都提供了哪些常用 API。

        1. empty()

        返回一個(gè)Optional容器對(duì)象,而不是 null。建議常用????

        2. of(T value)

        創(chuàng)建一個(gè)Optional對(duì)象,如果 value 是 null,則拋出 NPE。不建議用??

        3. ofNullable(T value)

        同上,創(chuàng)建一個(gè)Optional對(duì)象,但 value 為空時(shí)返回Optional.empty()。推薦使用?????

        4. get()

        返回Optional中包裝的值,在判空之前,千萬(wàn)不要直接使用!盡量別用!?

        5. orElse(T other)

        同樣是返回Optional中包裝的值,但不同的是當(dāng)取不到值時(shí),返回你指定的 default。看似很好,但不建議用??

        6. orElseGet(Supplier<? extends T> other)

        同樣是返回Optional中包裝的值,取不到值時(shí),返回你指定的 default。看似和 5 一樣,但推薦使用?????

        7. orElseThrow(Supplier<? extends X> exceptionSupplier)

        返回Optional中包裝的值,取不到值時(shí)拋出指定的異常。阻塞性業(yè)務(wù)場(chǎng)景推薦使用????

        8. isPresent()

        判斷Optional中是否有值,返回 boolean,某些情況下很有用,但盡量不要用在 if 判斷體中。可以用???

        9. ifPresent(Consumer<? super T> consumer)

        判斷Optional中是否有值,有值則執(zhí)行 consumer,否則什么都不干。日常情況下請(qǐng)使用這個(gè)????

        TIPS

        首先是一些基本原則:

        • 不要聲明任何Optional實(shí)例屬性
        • 不要在任何 setter 或者構(gòu)造方法中使用Optional
        • Optional屬于返回類(lèi)型,在業(yè)務(wù)返回值或者遠(yuǎn)程調(diào)用中使用
        1. 業(yè)務(wù)上需要空值時(shí),不要直接返回 null,使用Optional.empty()
        public Optional<User> getUser(String name) {
            if (StringUtil.isNotEmpty(name)) {
                return RemoteService.getUser(name);
            } 
            return Optional.empty();
        }
        2. 使用 orElseGet()

        獲取 value 有三種方式:get() orElse() orElseGet()。這里推薦在需要用到的地方只用 orElseGet()。

        首先,get()不能直接使用,需要結(jié)合判空使用。這和!=null其實(shí)沒(méi)多大區(qū)別,只是在表達(dá)和抽象上有所改善。

        其次,為什么不推薦orElse()呢?因?yàn)?code style="font-size: 14px;font-family: "Operator Mono", Consolas, Monaco, Menlo, monospace;padding: 2px 4px;border-radius: 4px;margin-right: 2px;margin-left: 2px;color: rgb(53, 179, 120);background-color: rgba(27, 31, 35, 0.05);word-break: break-all;">orElse()無(wú)論如何都會(huì)執(zhí)行括號(hào)中的內(nèi)容, orElseGet()只在主體 value 是空時(shí)執(zhí)行,下面看個(gè)例子:

        public String getName() {
            System.out.print("method called");
        }

        String name1 = Optional.of("String").orElse(getName()); //output: method called
        String name2 = Optional.of("String").orElseGet(() -> getName()); //output:

        如果上面的例子getName()方法是一個(gè)遠(yuǎn)程調(diào)用,或者涉及大量的文件 IO,代價(jià)可想而知。

        但 orElse()就一無(wú)是處嗎?并不是。orElseGet()需要構(gòu)建一個(gè)Supplier,如果只是簡(jiǎn)單的返回一個(gè)靜態(tài)資源、字符串等等,直接返回靜態(tài)資源即可。

        public static final String USER_STATUS = "UNKNOWN";
        ...
        public String findUserStatus(long id) {
            Optional<String> status = ... ; // 
            return status.orElse(USER_STATUS);
        }

        //不要這么寫(xiě)
        public String findUserStatus(long id) {
            Optional<String> status = ... ; // 
            return status.orElse("UNKNOWN");//這樣每次都會(huì)新建一個(gè)String對(duì)象
        }
        3. 使用 orElseThrow()

        這個(gè)針對(duì)阻塞性的業(yè)務(wù)場(chǎng)景比較合適,例如沒(méi)有從上游獲取到用戶(hù)信息,下面的所有操作都無(wú)法進(jìn)行,那此時(shí)就應(yīng)該拋出異常。正常的寫(xiě)法是先判空,再手動(dòng) throw 異常,現(xiàn)在可以集成為一行:

        public String findUser(long id) {
            Optional<User> user = remoteService.getUserById(id) ;
            return user.orElseThrow(IllegalStateException::new);
        }
        4. 不為空則執(zhí)行時(shí),使用 ifPresent()

        這點(diǎn)沒(méi)有性能上的優(yōu)勢(shì),但可以使代碼更簡(jiǎn)潔:

        //之前是這樣的
        if (status.isPresent()) {
            System.out.println("Status: " + status.get());
        }

        //現(xiàn)在
        status.ifPresent(System.out::println);
        5. 不要濫用

        有些簡(jiǎn)單明了的方法,完全沒(méi)必要增加Optional來(lái)增加復(fù)雜性。

        public String fetchStatus() {
            String status = getStatus() ;
            return Optional.ofNullable(status).orElse("PENDING");
        }

        //判斷一個(gè)簡(jiǎn)單的狀態(tài)而已
        public String fetchStatus() {
            String status = ... ;
            return status == null ? "PENDING" : status;
        }

        首先,null 可以作為集合的元素之一,它并不是非法的;其次,集合類(lèi)型本身已經(jīng)具備了完整的空表達(dá),再去包裝一層Optional也是徒增復(fù)雜,收益甚微。例如,map 已經(jīng)有了getOrDefault()這樣的類(lèi)似orElse()的 API 了。

        總結(jié)

        Optional的出現(xiàn)使 Java 對(duì) null 的表達(dá)能力更近了一步,好馬配好鞍,合理使用可以避免大量的 NPE,節(jié)省大量的人力物力。以上內(nèi)容也是本人查詢(xún)了很多資料,邊學(xué)邊寫(xiě)的產(chǎn)出,如有錯(cuò)漏之處,還請(qǐng)不吝指教。

        往期推薦

        令人笑噴的56個(gè)代碼注釋?zhuān)銓?xiě)過(guò)多少?

        還在用 Random生成隨機(jī)數(shù)?試試 ThreadLocalRandom,超好用!

        消息冪等(去重)通用解決方案,真頂!

        最強(qiáng)代碼生成器平臺(tái),殺瘋了!

        Spring為什么建議使用構(gòu)造器來(lái)注入?



        喜歡本文歡迎轉(zhuǎn)發(fā),關(guān)注我訂閱更多精彩

        關(guān)注我回復(fù)「加群」,加入Spring技術(shù)交流群

        瀏覽 47
        點(diǎn)贊
        評(píng)論
        收藏
        分享

        手機(jī)掃一掃分享

        分享
        舉報(bào)
        評(píng)論
        圖片
        表情
        推薦
        點(diǎn)贊
        評(píng)論
        收藏
        分享

        手機(jī)掃一掃分享

        分享
        舉報(bào)
        1. <strong id="7actg"></strong>
        2. <table id="7actg"></table>

        3. <address id="7actg"></address>
          <address id="7actg"></address>
          1. <object id="7actg"><tt id="7actg"></tt></object>
            91精品国自产在线观看 | 日本黄色小说网 | 老男人av | 黄色毛片免费 | 亚洲 成人 综合 另类 | 乱肉合集乱500小说怀孕 | 伊人精品视频在线观看 | 操逼无马赛克 | 国产精品二 | 日韩成人精品在线观看 |