【Java8新特性】史上最全Optional實(shí)戰(zhàn)教程,太厲害了!

大家好,我是Evan。

本文主要內(nèi)容如下:


目錄

一、前置基礎(chǔ)

Optional類源碼大量使用到:
1.四大函數(shù)式接口
2.lambda表達(dá)式

二、什么是Optional

1.Java 8新增了一個類 - Optional
2.Optional是一個容器,用于放置可能為空的值,它可以合理而優(yōu)雅的處理 null。
3.Optional的本質(zhì),就是內(nèi)部儲存了一個真實(shí)的值,在構(gòu)造的時候,就直接判斷其值是否為空
4.java.util.Optional<T>類本質(zhì)上就是一個容器,該容器的數(shù)值可以是空代表一個值不存在,也可以是非空代表一個值存在。
5.Optional類(java.util.Optional) 是一個容器類,代表一個值存在或不存在,原來用 null 表示一個值不存在,現(xiàn)在 Optional 可以更好的表達(dá)這個概念。并且可以避免空指針異常。

2.1理論拓展

??Monad 是一種用于處理副作用的編程模式,簡單來說就是將一些可能產(chǎn)生副作用的操作封裝起來,并在特定的作用域內(nèi)執(zhí)行,控制其對程序產(chǎn)生的影響。在函數(shù)式編程中經(jīng)常使用 Monad 模式來處理一些副作用,如 IO 操作、異常處理、狀態(tài)管理等。
Optional 是 Java 中一個非常典型的 Monad 實(shí)現(xiàn),它的主要作用是避免空指針異常并對可能為空的對象進(jìn)行封裝,并提供一系列函數(shù)式的操作,如 map()filter()flatMap()等方法,使代碼更加健壯、優(yōu)雅和安全。就像我們平時經(jīng)常對空值進(jìn)行判空處理一樣,Optional 提供了一種更加優(yōu)美和方便的方式,避免了深層次的嵌套判空,同時增加了代碼的可讀性和可維護(hù)性。
??對于函數(shù)式編程和 Monad 模式來說,這種方式是非常重要的,因?yàn)殡S著程序的規(guī)模增大,副作用也會越來越多,這時候避免副作用對程序的影響就變得尤為重要。通過使用 Monad 模式和類似 Optional 這樣的容器類型,我們可以更好地控制副作用,使程序更加穩(wěn)定和可靠。

三、為什么要用Optional

1.要是用來解決程序中常見的 NullPointerException異常問題。但是在實(shí)際開發(fā)過程中很多人都是在一知半解的使用 Optional,類似 if (userOpt.isPresent()){...}這樣的代碼隨處可見。如果是這樣我更愿意看到老老實(shí)實(shí)的 null 判斷,這樣強(qiáng)行使用 Optional反而增加了代碼的復(fù)雜度。
2.這是一個明確的警示,用于提示開發(fā)人員此處要注意null值。
3.不顯式的判空,當(dāng)出現(xiàn)俄羅斯式套娃判空時,代碼處理上更加優(yōu)雅。
4.使用 Optional 有時候可以很方便的過濾一些屬性,而且它的方法可以通過鏈?zhǔn)秸{(diào)用,方法間相互組合使用,使我們用少量的代碼就能完成復(fù)雜的邏輯。
5.防止空指針(NPE)、簡化if...else...判斷、減少代碼圈復(fù)雜度
6.Optional 之所以可以解決 NPE 的問題,是因?yàn)樗鞔_的告訴我們,不需要對它進(jìn)行判空。它就好像十字路口的路標(biāo),明確地告訴你該往哪走
7.很久很久以前,為了避免 NPE,我們會寫很多類似if (obj != null) {}的代碼,有時候忘記寫,就可能出現(xiàn) NPE,造成線上故障。在 Java 技術(shù)棧中,如果誰的代碼出現(xiàn)了 NPE,有極大的可能會被笑話,這個異常被很多人認(rèn)為是低級錯誤。Optional的出現(xiàn),可以讓大家更加輕松的避免因?yàn)榈图夊e誤被嘲諷的概率。
8.第一是改變我們傳統(tǒng)判空的方式(其實(shí)就是幫我們包裝了一層,判空的代碼幫我們寫了),用函數(shù)式編程和申明式編程來進(jìn)行對基本數(shù)據(jù)的校驗(yàn)和處理。第二就是聲明式的編程方式對閱讀代碼的人更友好。

3.1俄羅斯式套娃判空詳解

??手動進(jìn)行 if(obj!=null)的判空自然是最全能的,也是最可靠的,但是怕就怕俄羅斯套娃式的 if判空。
舉例一種情況:
為了獲?。菏?Province)→市(Ctiy)→區(qū)(District)→街道(Street)→道路名(Name)
作為一個“嚴(yán)謹(jǐn)且良心”的后端開發(fā)工程師,如果手動地進(jìn)行空指針保護(hù),我們難免會這樣寫:

public String getStreetName( Province province ) {
    if( province != null ) {
        City city = province.getCity();
        if( city != null ) {
            District district = city.getDistrict();
            if( district != null ) {
                Street street = district.getStreet();
                if( street != null ) {
                    return street.getName();
                }
            }
        }
    }
    return "未找到該道路名";
}
為了獲取到鏈條最終端的目的值,直接鏈?zhǔn)饺≈当囟ㄓ袉栴},因?yàn)橹虚g只要某一個環(huán)節(jié)的對象為 null,則代碼一定會炸,并且拋出 NullPointerException異常,然而俄羅斯套娃式的 if判空實(shí)在有點(diǎn)心累。
Optional接口本質(zhì)是個容器,你可以將你可能為 null的變量交由它進(jìn)行托管,這樣我們就不用顯式對原變量進(jìn)行 null值檢測,防止出現(xiàn)各種空指針異常。
Optional語法專治上面的俄羅斯套娃式 if 判空,因此上面的代碼可以重構(gòu)如下:

public String getStreetName( Province province ) {
    return Optional.ofNullable( province )
            .map( i -> i.getCity() )
            .map( i -> i.getDistrict() )
            .map( i -> i.getStreet() )
            .map( i -> i.getName() )
            .orElse( "未找到該道路名" );
}

漂亮!嵌套的 if/else判空灰飛煙滅!
解釋一下執(zhí)行過程:
ofNullable(province ) :它以一種智能包裝的方式來構(gòu)造一個 Optional實(shí)例, province是否為 null均可以。如果為 null,返回一個單例空 Optional對象;如果非 null,則返回一個 Optional包裝對象
map(xxx ):該函數(shù)主要做值的轉(zhuǎn)換,如果上一步的值非 null,則調(diào)用括號里的具體方法進(jìn)行值的轉(zhuǎn)化;反之則直接返回上一步中的單例 Optional包裝對象
orElse(xxx ):很好理解,在上面某一個步驟的值轉(zhuǎn)換終止時進(jìn)行調(diào)用,給出一個最終的默認(rèn)值

四、Optional基本知識

Optional類常用方法:

Optional.of(T t) : 創(chuàng)建一個 Optional 實(shí)例。

Optional.empty() : 創(chuàng)建一個空的 Optional 實(shí)例。

Optional.ofNullable(T t):若 t 不為 null,創(chuàng)建 Optional 實(shí)例,否則創(chuàng)建空實(shí)例。

isPresent() : 判斷是否包含值。

orElse(T t) : 如果調(diào)用對象包含值,返回該值,否則返回t。

orElseGet(Supplier s) :如果調(diào)用對象包含值,返回該值,否則返回 s 獲取的值。

map(Function f): 如果有值對其處理,并返回處理后的Optional,否則返回 Optional.empty()。

flatMap(Function mapper):與 map 類似,要求返回值必須是Optional。

4.1API的思考

1.of(T value)
一個東西存在那么自然有存在的價值。當(dāng)我們在運(yùn)行過程中,不想隱藏NullPointerException。
而是要立即報告,這種情況下就用Of函數(shù)。但是不得不承認(rèn),這樣的場景真的很少。我也僅在寫junit測試用例中用到過此函數(shù)。

2.get()
直觀從語義上來看,get() 方法才是最正宗的獲取 Optional 對象值的方法,
但很遺憾,該方法是有缺陷的,因?yàn)榧偃?Optional 對象的值為 null,該方法會拋出 NoSuchElementException 異常。這完全與我們使用 Optional 類的初衷相悖。

五、工作中如何正確使用Optional

5.1 orElseThrow

orElseThrow()方法當(dāng)遇到一個不存在的值的時候,并不返回一個默認(rèn)值,而是拋出異常。

public void validateRequest(String requestId) {
    Optional.ofNullable(requestId)
            .orElseThrow(() -> new IllegalArgumentException("請求編號不能為空"));
    // 執(zhí)行后續(xù)操作
}
Optional<User> optionalUser = Optional.ofNullable(null);
User user = optionalUser.orElseThrow(() -> new RuntimeException("用戶不存在"));

// 傳入 null 參數(shù),獲取一個 Optional 對象,并使用 orElseThrow 方法
    try {
        Optional optional2 = Optional.ofNullable(null);
        Object object2 = optional2.orElseThrow(() -> {
                    System.out.println("執(zhí)行邏輯,然后拋出異常");
                    return new RuntimeException("拋出異常");
                }
        );
        System.out.println("輸出的值為:" + object2);
    } catch (Throwable throwable) {
        throwable.printStackTrace();
    }

5.2 filter

接收一個函數(shù)式接口,當(dāng)符合接口時,則返回一個Optional對象,否則返回一個空的Optional對象。
例如,我們需要過濾出年齡在25歲到35歲之前的人群,那在Java8之前我們需要創(chuàng)建一個如下的方法來檢測每個人的年齡范圍是否在25歲到35歲之前。

public boolean filterPerson(Peron person){
    boolean isInRange = false;
    if(person != null && person.getAge() >= 25 && person.getAge() <= 35){
        isInRange =  true;
    }
    return isInRange;
}

public boolean filterPersonByOptional(Peron person){
     return Optional.ofNullable(person)
       .map(Peron::getAge)
       .filter(p -> p >= 25)
       .filter(p -> p <= 35)
       .isPresent();
}
使用Optional看上去就清爽多了,這里,map()僅僅是將一個值轉(zhuǎn)換為另一個值,并且這個操作并不會改變原來的值。

     public class OptionalMapFilterDemo {
    public static void main(String[] args) {
        String password = "password";
        Optional<String>  opt = Optional.ofNullable(password);

        Predicate<String> len6 = pwd -> pwd.length() > 6;
        Predicate<String> len10 = pwd -> pwd.length() < 10;
        Predicate<String> eq = pwd -> pwd.equals("password");

        boolean result = opt.map(String::toLowerCase).filter(len6.and(len10 ).and(eq)).isPresent();
        System.out.println(result);
    }
}

5.3 orElse和orElseGet

結(jié)論:當(dāng)optional.isPresent() == false時,orElse()和orElseGet()沒有區(qū)別;
而當(dāng)optional.isPresent() == true時,無論你是否需要,orElse始終會調(diào)用后續(xù)函數(shù)。

若方法不是純計算型的,使用Optional的orElse(T);
若有與數(shù)據(jù)庫交互或者遠(yuǎn)程調(diào)用的,都應(yīng)該使用orElseGet(Supplier)。
推薦使用orElseGet ,當(dāng)存在一些復(fù)合操作,遠(yuǎn)程調(diào)用,磁盤io等大開銷的動作禁止使用orElse。
原因:當(dāng)value不為空時,orElse仍然會執(zhí)行。

public class GetValueDemo {
    public static String getDefaultName() {
        System.out.println("Getting Default Name");
        return "binghe";
    }

    public static void main(String[] args) {
/*        String text = null;
        System.out.println("Using orElseGet:");
        String defaultText = Optional.ofNullable(text).orElseGet(GetValueDemo::getDefaultName);
        assertEquals("binghe", defaultText);
        System.out.println("Using orElse:");
        defaultText = Optional.ofNullable(text).orElse(GetValueDemo.getDefaultName());
        assertEquals("binghe", defaultText);*/

        // TODO: 2023/5/13 重點(diǎn)示例
        String name = "binghe001";

        System.out.println("Using orElseGet:");
        String defaultName = Optional.ofNullable(name).orElseGet(GetValueDemo::getDefaultName);
        assertEquals("binghe001", defaultName);

        System.out.println("Using orElse:");
        defaultName = Optional.ofNullable(name).orElse(getDefaultName());
        assertEquals("binghe001", defaultName);
    }  
}    

運(yùn)行結(jié)果如下所示。
Using orElseGet:
Using orElse:
Getting default name...
可以看到,當(dāng)使用orElseGet()方法時,getDefaultName()方法并不執(zhí)行,因?yàn)镺ptional中含有值,而使用orElse時則照常執(zhí)行。所以可以看到,當(dāng)值存在時,orElse相比于orElseGet,多創(chuàng)建了一個對象。如果創(chuàng)建對象時,存在網(wǎng)絡(luò)交互,那系統(tǒng)資源的開銷就比較大了,這是需要我們注意的一個地方。

5.4 map和flatMap

        String len = null;
        Integer integer = Optional.ofNullable(len)
                .map(s -> s.length())
                .orElse(0);
        System.out.println("integer = " + integer);


        Person person = new Person("evan", 18);
        Optional.ofNullable(person)
                .map(p -> p.getName())
                .orElse("");

        Optional.ofNullable(person)
                .flatMap(p -> Optional.ofNullable(p.getName()))
                .orElse("");

注意:方法getName返回的是一個Optional對象,如果使用map,我們還需要再調(diào)用一次get()方法,而使用flatMap()就不需要了。

5.5 項(xiàng)目實(shí)戰(zhàn)

實(shí)戰(zhàn)一

public class OptionalExample {
    /**
     * 測試的 main 方法
     */
    public static void main(String[] args) {
        // 創(chuàng)建一個測試的用戶集合
        List<User> userList = new ArrayList<>();

        // 創(chuàng)建幾個測試用戶
        User user1 = new User("abc");
        User user2 = new User("efg");
        User user3 = null;

        // 將用戶加入集合
        userList.add(user1);
        userList.add(user2);
        userList.add(user3);

        // 創(chuàng)建用于存儲姓名的集合
        List<String> nameList = new ArrayList();
        List<User> nameList03 = new ArrayList();
        List<String> nameList04 = new ArrayList();
        // 循環(huán)用戶列表獲取用戶信息,值獲取不為空且用戶以 a 開頭的姓名,
        // 如果不符合條件就設(shè)置默認(rèn)值,最后將符合條件的用戶姓名加入姓名集合
/*
        for (User user : userList) {
            nameList.add(Optional.ofNullable(user).map(User::getName).filter(value -> value.startsWith("a")).orElse("未填寫"));
        }
*/

        // 輸出名字集合中的值
/*        System.out.println("通過 Optional 過濾的集合輸出:");
        System.out.println("nameList.size() = " + nameList.size());
        nameList.stream().forEach(System.out::println);*/


/*        Optional.ofNullable(userList)
                .ifPresent(u -> {
                    for (User user : u) {
                        nameList04.add(Optional.ofNullable(user).map(User::getName).filter(f -> f.startsWith("e")).orElse("無名"));
                    }
                });*/

        Optional.ofNullable(userList)
                .ifPresent(u -> {
                   u.forEach(m->{
                       Optional<String> stringOptional = Optional.ofNullable(m).map(User::getName).filter(f -> f.startsWith("a"));
                       stringOptional.ifPresent(nameList04::add);
                   });
                });
        System.out.println("nameList04.size() = " + nameList04.size());
        nameList04.forEach(System.err::println);


        Optional.ofNullable(userList).ifPresent(nameList03::addAll);
        System.out.println("nameList03.size() = " + nameList03.size());
        nameList03.stream().forEach(System.err::println);

    }

}

實(shí)戰(zhàn)二

以前寫法
public String getCity(User user)  throws Exception{
        if(user!=null){
            if(user.getAddress()!=null){
                Address address = user.getAddress();
                if(address.getCity()!=null){
                    return address.getCity();
                }
            }
        }
        throw new Excpetion("取值錯誤"); 
    }


    public String getCity(User user) throws Exception{
    return Optional.ofNullable(user)
                   .map(u-> u.getAddress())
                   .map(a->a.getCity())
                   .orElseThrow(()->new Exception("取指錯誤"));
}

實(shí)戰(zhàn)三 簡化if.else

以前寫法
public User getUser(User user) throws Exception{
    if(user!=null){
        String name = user.getName();
        if("zhangsan".equals(name)){
            return user;
        }
    }else{
        user = new User();
        user.setName("zhangsan");
        return user;
    }
}

java8寫法
public User getUser(User user) {
    return Optional.ofNullable(user)
                   .filter(u->"zhangsan".equals(u.getName()))
                   .orElseGet(()-> {
                        User user1 = new User();
                        user1.setName("zhangsan");
                        return user1;
                   });
}

實(shí)戰(zhàn)四 解決checkStyle問題


BaseMasterSlaveServersConfig smssc = new BaseMasterSlaveServersConfig();
if (clientName != null) {
    smssc.setClientName(clientName);
}
if (idleConnectionTimeout != null) {
    smssc.setIdleConnectionTimeout(idleConnectionTimeout);
}
if (connectTimeout != null) {
    smssc.setConnectTimeout(connectTimeout);
}
if (timeout != null) {
    smssc.setTimeout(timeout);
}
if (retryAttempts != null) {
    smssc.setRetryAttempts(retryAttempts);
}
if (retryInterval != null) {
    smssc.setRetryInterval(retryInterval);
}
if (reconnectionTimeout != null) {
    smssc.setReconnectionTimeout(reconnectionTimeout);
}
if (password != null) {
    smssc.setPassword(password);
}
if (failedAttempts != null) {
    smssc.setFailedAttempts(failedAttempts);
}
// ...后面還有很多這種判斷,一個if就是一個分支,會增長圈復(fù)雜度


改造后:
Optional.ofNullable(clientName).ifPresent(smssc::setClientName);
Optional.ofNullable(idleConnectionTimeout).ifPresent(smssc::setIdleConnectionTimeout);
Optional.ofNullable(connectTimeout).ifPresent(smssc::setConnectTimeout);
Optional.ofNullable(timeout).ifPresent(smssc::setTimeout);
Optional.ofNullable(retryAttempts).ifPresent(smssc::setRetryAttempts);
Optional.ofNullable(retryInterval).ifPresent(smssc::setRetryInterval);
Optional.ofNullable(reconnectionTimeout).ifPresent(smssc::setReconnectionTimeout);
// ...縮減為一行,不但減少了圈復(fù)雜度,而且減少了行數(shù)

實(shí)戰(zhàn)五 Optional提升代碼的可讀性

傳統(tǒng)操作:

public class ReadExample {
    //    舉個栗子:你拿到了用戶提交的新密碼,你要判斷用戶的新密碼是否符合設(shè)置密碼的規(guī)則,比如長度要超過八位數(shù),然后你要對用戶的密碼進(jìn)行加密。
    private static String newPSWD = "12345679";
    public static void main(String[] args) throws Exception {
        // 簡單的清理
        newPSWD = ObjectUtil.isEmpty(newPSWD) ? "" : newPSWD.trim();
        // 是否符合密碼策略
        if (newPSWD.length() <= 8) throw new Exception("Password rules are not met: \n" + newPSWD);
        // 加密
        //將 MD5 值轉(zhuǎn)換為 16 進(jìn)制字符串
        try {
            final MessageDigest md5 = MessageDigest.getInstance("MD5");
            md5.update(newPSWD.getBytes(StandardCharsets.UTF_8));
            newPSWD = new BigInteger(1, md5.digest()).toString(16);
        } catch (
                NoSuchAlgorithmException e) {
            System.out.println("Encryption failed");
        }
        System.out.println("We saved a new password for the user: \n" + newPSWD);
    }
}

優(yōu)化版本:

優(yōu)化一:
public class BetterReadExample {
    //    舉個栗子:你拿到了用戶提交的新密碼,你要判斷用戶的新密碼是否符合設(shè)置密碼的規(guī)則,比如長度要超過八位數(shù),然后你要對用戶的密碼進(jìn)行加密。
    private static String newPSWD = "888888888";
    public static void main(String[] args) throws Exception {

        Function<String, String> md = (o) -> {
            try {
                final MessageDigest md5;
                md5 = MessageDigest.getInstance("MD5");
                md5.update(o.getBytes(StandardCharsets.UTF_8));
                return new BigInteger(1, md5.digest()).toString(16);
            } catch (NoSuchAlgorithmException e) {
                throw new RuntimeException("Encryption failed");
            }
        };


        String digestpwd;
        digestpwd = Optional.ofNullable(newPSWD)
                            .map(String::trim)
                            .filter(f -> f.length() > 8)
                            .map(md)
                            .orElseThrow(() -> new RuntimeException("Incorrect saving new password"));
        System.err.println("digestpwd = " + digestpwd);

    }
}

優(yōu)化二:
/**
 *增加可讀性
 */
public class BetterReadExample02 {
    //    舉個栗子:你拿到了用戶提交的新密碼,你要判斷用戶的新密碼是否符合設(shè)置密碼的規(guī)則,比如長度要超過八位數(shù),然后你要對用戶的密碼進(jìn)行加密。
    private static String newPSWD = "888888888";

    //清除
    private static String clean(String s){
        return s.trim();
    }

    private static boolean filterPw(String s){
        return s.length()>8;
    }

    private static RuntimeException myREx() {
        return new RuntimeException("Incorrect saving new password");
    }

    public static void main(String[] args) throws Exception {
        //項(xiàng)目實(shí)戰(zhàn)中,把main方法里面的代碼再抽出一個獨(dú)立方法
        Function<String, String> md = (o) -> {
            try {
                final MessageDigest md5;
                md5 = MessageDigest.getInstance("MD5");
                md5.update(o.getBytes(StandardCharsets.UTF_8));
                return new BigInteger(1, md5.digest()).toString(16);
            } catch (NoSuchAlgorithmException e) {
                throw new RuntimeException("Encryption failed");
            }
        };

        String digestpwd;
        digestpwd = Optional.ofNullable(newPSWD)
                            .map(BetterReadExample02::clean)
                            .filter(BetterReadExample02::filterPw)
                            .map(md)
                            .orElseThrow(BetterReadExample02::myREx);
        System.err.println("digestpwd = " + digestpwd);

    }
}

實(shí)戰(zhàn)六 大膽重構(gòu)代碼

//1. map 示例
if ( hero != null){
   return "hero : " + hero.getName() + " is fire...";
 } else { 
   return "angela";
 }
 //重構(gòu)成
 String heroName = hero
 .map(this::printHeroName)
 .orElseGet(this::getDefaultName);

public void printHeroName(Hero dog){
   return  "hero : " + hero.getName() + " is fire...";
}
public void getDefaultName(){
   return "angela";
}

//2. filter示例
Hero hero = fetchHero();
if(hero != null && hero.hasBlueBuff()){
  hero.fire();
}

//重構(gòu)成
Optional<Hero> optionalHero = fetchHero();
optionalHero
 .filter(Hero::hasBlueBuff)
 .ifPresent(this::fire);

實(shí)戰(zhàn)七 舍棄三目運(yùn)算

//第一種判空
if (Objects.notNull(taskNode.getFinishTime())) {
  taskInfoVo.set(taskNode.getFinishTime().getTime());
}
//第二種判空 保留builder模式
TaskInfoVo
.builder()
.finishTime(taskNode.getFinishTime() == null ? null : taskNode.getFinishTime().getTime())
.build()));

//第三種判空
public Result<TaskInfoVo> getTaskInfo(String taskId){
  TaskNode taskNode = taskExecutor.getByTaskId(String taskId);
  //返回任務(wù)視圖
  TaskInfoVo taskInfoVo = TaskInfoVo
                      .builder()
                      .taskName(taskNode.getName())
                      .finishTime(Optional.ofNullable(taskNode.getFinishTime()).map(date ->date.getTime()).orElse(null))
             .user(taskNode.getUser())
                     .memo(taskNode.getMemo())
                     .build()));;

  return Result.ok(taskInfoVo);
}

六、Optional操作總結(jié)

NPE 之所以討厭,就是只要出現(xiàn) NPE,我們就能夠解決。但是一旦出現(xiàn),都已經(jīng)是事后,可能已經(jīng)出現(xiàn)線上故障。偏偏在 Java 語言中,NPE 又很容易出現(xiàn)。Optional提供了模板方法,有效且高效的避免 NPE。

接下來,我們針對上面的使用,總結(jié)一下:
Optional是一個包裝類,且不可變,不可序列化
沒有公共構(gòu)造函數(shù),創(chuàng)建需要使用of、ofNullable方法
空Optional是單例,都是引用Optional.EMPTY
想要獲取Optional的值,可以使用get、orElse、orElseGet、orElseThrow

另外,還有一些實(shí)踐上的建議:
使用get方法前,必須使用isPresent檢查。但是使用isPresent前,先思考下是否可以使用orElse、orElseGet等方法代替實(shí)現(xiàn)。
orElse和orElseGet,優(yōu)先選擇orElseGet,這個是惰性計算
Optional不要作為參數(shù)或者類屬性,可以作為返回值
盡量將map、filter的函數(shù)參數(shù)抽出去作為單獨(dú)方法,這樣能夠保持鏈?zhǔn)秸{(diào)用
不要將null賦給Optional 雖然Optional支持null值,但是不要顯示的把null 傳遞給Optional
盡量避免使用Optional.get()
當(dāng)結(jié)果不確定是否為null時,且需要對結(jié)果做下一步處理,使用Optional;
在類、集合中盡量不要使用Optional 作為基本元素;
盡量不要在方法參數(shù)中傳遞Optional;
不要使用 Optional 作為Java Bean Setter方法的參數(shù)
因?yàn)镺ptional 是不可序列化的,而且降低了可讀性。
不要使用Optional作為Java Bean實(shí)例域的類型
原因同上。

七、Optional錯誤使用

1.使用在 POJO 中

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

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

2.使用在注入的屬性中
這種寫法估計用的人會更少,但不排除有腦洞的。

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)該終止并報異常,并不是無聲無息,讓其看起來什么問題都沒有。

  1. 直接使用 isPresent() 進(jìn)行 if 檢查
    這個直接參考上面的例子,用 if判斷和 1.8 之前的寫法并沒有什么區(qū)別,反而返回值包了一層 Optional,增加了代碼的復(fù)雜性,沒有帶來任何實(shí)質(zhì)的收益。其實(shí) isPresent()一般用于流處理的結(jié)尾,用于判斷是否符合條件。
list.stream()
    .filer(x -> Objects.equals(x,param))
    .findFirst()
    .isPresent()
  1. 在方法參數(shù)中使用 Optional
    我們用一個東西之前得想明白,這東西是為解決什么問題而誕生的。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
}   

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

如對您有幫助,歡迎點(diǎn)贊,嘿嘿 !!!

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,546評論 6 533
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 98,570評論 3 418
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,505評論 0 376
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,017評論 1 313
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 71,786評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 55,219評論 1 324
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼。 笑死,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,287評論 3 441
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 42,438評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 48,971評論 1 335
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 40,796評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 42,995評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,540評論 5 359
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 44,230評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,662評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,918評論 1 286
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,697評論 3 392
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 47,991評論 2 374