<kbd id="5sdj3"></kbd>
<th id="5sdj3"></th>

  • <dd id="5sdj3"><form id="5sdj3"></form></dd>
    <td id="5sdj3"><form id="5sdj3"><big id="5sdj3"></big></form></td><del id="5sdj3"></del>

  • <dd id="5sdj3"></dd>
    <dfn id="5sdj3"></dfn>
  • <th id="5sdj3"></th>
    <tfoot id="5sdj3"><menuitem id="5sdj3"></menuitem></tfoot>

  • <td id="5sdj3"><form id="5sdj3"><menu id="5sdj3"></menu></form></td>
  • <kbd id="5sdj3"><form id="5sdj3"></form></kbd>

    Java8 Optional 最佳實(shí)踐!

    共 7558字,需瀏覽 16分鐘

     ·

    2021-09-12 18:37

    作者:ES_her0

    來源:xie.infoq.cn/article/e3d1f0f4f095397c44812a5be

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

    來自作者的說明

    首先我們來看一下Optional的作者 Brian Goetz 對這個(gè) API 的說明:

    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帶來的錯(cuò)誤,我們提供了一個(gè)可以明確表示空值的有限的機(jī)制。

    基礎(chǔ)理解

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

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

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

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

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

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

    看起來,好像比之前好了一些,至少看起來沒那么笨。但如果采用寫法 1,好像更啰嗦了。

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

    下面先說一些在我看來不合適的使用方式:

    Bad Practice

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

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

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

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

    我們用一個(gè)東西之前得想明白,這東西是為解決什么問題而誕生的。Optional直白一點(diǎn)說就是為了表達(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)在那種所謂的著急上線,著急交付,對Optional也不是很熟悉,直接就用了。這里多說一句,可能有人會(huì)反問了:甲方/業(yè)務(wù)著急,需求又多,哪有時(shí)間給他去做優(yōu)化???因?yàn)槲以诂F(xiàn)實(shí)工作中遇到過,但這兩者并不矛盾,因?yàn)榇a行數(shù)上差別并不大,只要自己平時(shí)保持學(xué)習(xí),都是信手拈來的東西。

    4. 使用在 POJO 中

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

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

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

    5. 使用在注入的屬性中

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

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

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

    Best and Pragmatic Practice

    API

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

    1. empty()

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

    2. of(T value)

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

    3. ofNullable(T value)

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

    4. get()

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

    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ù)場景推薦使用????

    8. isPresent()

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

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

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

    TIPS

    首先是一些基本原則:

    • 不要聲明任何Optional實(shí)例屬性
    • 不要在任何 setter 或者構(gòu)造方法中使用Optional
    • Optional屬于返回類型,在業(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í)沒多大區(qū)別,只是在表達(dá)和抽象上有所改善。

    其次,為什么不推薦orElse()呢?因?yàn)?code style="margin-right: 2px;margin-left: 2px;padding: 2px 4px;max-width: 100%;font-size: 14px;font-family: "Operator Mono", Consolas, Monaco, Menlo, monospace;border-radius: 4px;color: rgb(53, 179, 120);background-color: rgba(27, 31, 35, 0.05);word-break: break-all;box-sizing: border-box !important;overflow-wrap: break-word !important;">orElse()無論如何都會(huì)執(zhí)行括號中的內(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()就一無是處嗎?并不是。orElseGet()需要構(gòu)建一個(gè)Supplier,如果只是簡單的返回一個(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);
    }

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

    這個(gè)針對阻塞性的業(yè)務(wù)場景比較合適,例如沒有從上游獲取到用戶信息,下面的所有操作都無法進(jìn)行,那此時(shí)就應(yīng)該拋出異常。正常的寫法是先判空,再手動(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)沒有性能上的優(yōu)勢,但可以使代碼更簡潔:

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

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

    有些簡單明了的方法,完全沒必要增加Optional來增加復(fù)雜性。

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

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

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

    總結(jié)

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

    程序汪資料鏈接

    程序汪接的7個(gè)私活都在這里,經(jīng)驗(yàn)整理

    Java項(xiàng)目分享  最新整理全集,找項(xiàng)目不累啦 04版

    堪稱神級的Spring Boot手冊,從基礎(chǔ)入門到實(shí)戰(zhàn)進(jìn)階

    臥槽!字節(jié)跳動(dòng)《算法中文手冊》火了,完整版 PDF 開放下載!

    臥槽!阿里大佬總結(jié)的《圖解Java》火了,完整版PDF開放下載!

    字節(jié)跳動(dòng)總結(jié)的設(shè)計(jì)模式 PDF 火了,完整版開放下載!

    歡迎添加程序汪個(gè)人微信 itwang008  進(jìn)粉絲群或圍觀朋友圈

    瀏覽 49
    點(diǎn)贊
    評論
    收藏
    分享

    手機(jī)掃一掃分享

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

    手機(jī)掃一掃分享

    分享
    舉報(bào)

    <kbd id="5sdj3"></kbd>
    <th id="5sdj3"></th>

  • <dd id="5sdj3"><form id="5sdj3"></form></dd>
    <td id="5sdj3"><form id="5sdj3"><big id="5sdj3"></big></form></td><del id="5sdj3"></del>

  • <dd id="5sdj3"></dd>
    <dfn id="5sdj3"></dfn>
  • <th id="5sdj3"></th>
    <tfoot id="5sdj3"><menuitem id="5sdj3"></menuitem></tfoot>

  • <td id="5sdj3"><form id="5sdj3"><menu id="5sdj3"></menu></form></td>
  • <kbd id="5sdj3"><form id="5sdj3"></form></kbd>
    日日舔| 国产乱人妻精品入口 | 欧美成人精品三级在线网站 | 亚洲无码视频播放免费 | 可 尻屄网站 |