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

大家好,我是Evan辙芍。

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


目錄

一啡彬、前置基礎(chǔ)

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

二、什么是Optional

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

2.1理論拓展

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

三昨登、為什么要用Optional

1.要是用來解決程序中常見的 NullPointerException異常問題趾代。但是在實際開發(fā)過程中很多人都是在一知半解的使用 Optional,類似 if (userOpt.isPresent()){...}這樣的代碼隨處可見丰辣。如果是這樣我更愿意看到老老實實的 null 判斷撒强,這樣強行使用 Optional反而增加了代碼的復(fù)雜度禽捆。
2.這是一個明確的警示,用于提示開發(fā)人員此處要注意null值飘哨。
3.不顯式的判空胚想,當出現(xiàn)俄羅斯式套娃判空時,代碼處理上更加優(yōu)雅芽隆。
4.使用 Optional 有時候可以很方便的過濾一些屬性浊服,而且它的方法可以通過鏈式調(diào)用,方法間相互組合使用胚吁,使我們用少量的代碼就能完成復(fù)雜的邏輯牙躺。
5.防止空指針(NPE)、簡化if...else...判斷囤采、減少代碼圈復(fù)雜度
6.Optional 之所以可以解決 NPE 的問題述呐,是因為它明確的告訴我們,不需要對它進行判空蕉毯。它就好像十字路口的路標乓搬,明確地告訴你該往哪走
7.很久很久以前,為了避免 NPE代虾,我們會寫很多類似if (obj != null) {}的代碼进肯,有時候忘記寫,就可能出現(xiàn) NPE棉磨,造成線上故障江掩。在 Java 技術(shù)棧中,如果誰的代碼出現(xiàn)了 NPE乘瓤,有極大的可能會被笑話环形,這個異常被很多人認為是低級錯誤。Optional的出現(xiàn)衙傀,可以讓大家更加輕松的避免因為低級錯誤被嘲諷的概率抬吟。
8.第一是改變我們傳統(tǒng)判空的方式(其實就是幫我們包裝了一層,判空的代碼幫我們寫了)统抬,用函數(shù)式編程和申明式編程來進行對基本數(shù)據(jù)的校驗和處理火本。第二就是聲明式的編程方式對閱讀代碼的人更友好。

3.1俄羅斯式套娃判空詳解

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

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 "未找到該道路名";
}
為了獲取到鏈條最終端的目的值挥下,直接鏈式取值必定有問題叔锐,因為中間只要某一個環(huán)節(jié)的對象為 null挪鹏,則代碼一定會炸见秽,并且拋出 NullPointerException異常愉烙,然而俄羅斯套娃式的 if判空實在有點心累。
Optional接口本質(zhì)是個容器解取,你可以將你可能為 null的變量交由它進行托管步责,這樣我們就不用顯式對原變量進行 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實例蔗包, province是否為 null均可以。如果為 null慧邮,返回一個單例空 Optional對象调限;如果非 null,則返回一個 Optional包裝對象
map(xxx ):該函數(shù)主要做值的轉(zhuǎn)換误澳,如果上一步的值非 null耻矮,則調(diào)用括號里的具體方法進行值的轉(zhuǎn)化;反之則直接返回上一步中的單例 Optional包裝對象
orElse(xxx ):很好理解忆谓,在上面某一個步驟的值轉(zhuǎn)換終止時進行調(diào)用裆装,給出一個最終的默認值

四、Optional基本知識

Optional類常用方法:

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

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

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

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)
一個東西存在那么自然有存在的價值吏饿。當我們在運行過程中踪危,不想隱藏NullPointerException蔬浙。
而是要立即報告,這種情況下就用Of函數(shù)贞远。但是不得不承認畴博,這樣的場景真的很少。我也僅在寫junit測試用例中用到過此函數(shù)蓝仲。

2.get()
直觀從語義上來看俱病,get() 方法才是最正宗的獲取 Optional 對象值的方法,
但很遺憾袱结,該方法是有缺陷的亮隙,因為假如 Optional 對象的值為 null,該方法會拋出 NoSuchElementException 異常垢夹。這完全與我們使用 Optional 類的初衷相悖溢吻。

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

5.1 orElseThrow

orElseThrow()方法當遇到一個不存在的值的時候果元,并不返回一個默認值促王,而是拋出異常。

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ù)式接口欣硼,當符合接口時题翰,則返回一個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é)論:當optional.isPresent() == false時,orElse()和orElseGet()沒有區(qū)別够滑;
而當optional.isPresent() == true時垦写,無論你是否需要,orElse始終會調(diào)用后續(xù)函數(shù)彰触。

若方法不是純計算型的梯投,使用Optional的orElse(T);
若有與數(shù)據(jù)庫交互或者遠程調(diào)用的,都應(yīng)該使用orElseGet(Supplier)分蓖。
推薦使用orElseGet 尔艇,當存在一些復(fù)合操作,遠程調(diào)用么鹤,磁盤io等大開銷的動作禁止使用orElse终娃。
原因:當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 重點示例
        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);
    }  
}    

運行結(jié)果如下所示尝抖。
Using orElseGet:
Using orElse:
Getting default name...
可以看到,當使用orElseGet()方法時迅皇,getDefaultName()方法并不執(zhí)行,因為Optional中含有值衙熔,而使用orElse時則照常執(zhí)行登颓。所以可以看到,當值存在時红氯,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 項目實戰(zhàn)

實戰(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è)置默認值拿撩,最后將符合條件的用戶姓名加入姓名集合
/*
        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);

    }

}

實戰(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("取指錯誤"));
}

實戰(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;
                   });
}

實戰(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ù)

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

傳統(tǒng)操作:

public class ReadExample {
    //    舉個栗子:你拿到了用戶提交的新密碼错邦,你要判斷用戶的新密碼是否符合設(shè)置密碼的規(guī)則探赫,比如長度要超過八位數(shù),然后你要對用戶的密碼進行加密兴猩。
    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 進制字符串
        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ù),然后你要對用戶的密碼進行加密讨勤。
    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ù)潭千,然后你要對用戶的密碼進行加密谱姓。
    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 {
        //項目實戰(zhàn)中,把main方法里面的代碼再抽出一個獨立方法
        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);

    }
}

實戰(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);

實戰(zhà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

另外尝哆,還有一些實踐上的建議:
使用get方法前,必須使用isPresent檢查甜攀。但是使用isPresent前秋泄,先思考下是否可以使用orElse、orElseGet等方法代替實現(xiàn)规阀。
orElse和orElseGet恒序,優(yōu)先選擇orElseGet,這個是惰性計算
Optional不要作為參數(shù)或者類屬性谁撼,可以作為返回值
盡量將map歧胁、filter的函數(shù)參數(shù)抽出去作為單獨方法,這樣能夠保持鏈式調(diào)用
不要將null賦給Optional 雖然Optional支持null值,但是不要顯示的把null 傳遞給Optional
盡量避免使用Optional.get()
當結(jié)果不確定是否為null時喊巍,且需要對結(jié)果做下一步處理屠缭,使用Optional;
在類崭参、集合中盡量不要使用Optional 作為基本元素呵曹;
盡量不要在方法參數(shù)中傳遞Optional;
不要使用 Optional 作為Java Bean Setter方法的參數(shù)
因為Optional 是不可序列化的何暮,而且降低了可讀性奄喂。
不要使用Optional作為Java Bean實例域的類型
原因同上。

七海洼、Optional錯誤使用

1.使用在 POJO 中

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

這樣的寫法將會給序列化帶來麻煩跨新,Optional本身并沒有實現(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() 進行 if 檢查
    這個直接參考上面的例子,用 if判斷和 1.8 之前的寫法并沒有什么區(qū)別球化,反而返回值包了一層 Optional秽晚,增加了代碼的復(fù)雜性,沒有帶來任何實質(zhì)的收益筒愚。其實 isPresent()一般用于流處理的結(jié)尾赴蝇,用于判斷是否符合條件。
list.stream()
    .filer(x -> Objects.equals(x,param))
    .findFirst()
    .isPresent()
  1. 在方法參數(shù)中使用 Optional
    我們用一個東西之前得想明白巢掺,這東西是為解決什么問題而誕生的句伶。Optional直白一點說就是為了表達可空性,如果方法參數(shù)可以為空陆淀,為何不重載呢败晴?包括使用構(gòu)造函數(shù)也一樣拆檬。重載的業(yè)務(wù)表達更加清晰直觀。
//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)化坝:摺哀九?因為我在現(xiàn)實工作中遇到過,但這兩者并不矛盾搅幅,因為代碼行數(shù)上差別并不大阅束,只要自己平時保持學習,都是信手拈來的東西茄唐。

如對您有幫助息裸,歡迎點贊,嘿嘿 !!!

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末沪编,一起剝皮案震驚了整個濱河市呼盆,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌蚁廓,老刑警劉巖访圃,帶你破解...
    沈念sama閱讀 211,348評論 6 491
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異相嵌,居然都是意外死亡腿时,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,122評論 2 385
  • 文/潘曉璐 我一進店門饭宾,熙熙樓的掌柜王于貴愁眉苦臉地迎上來批糟,“玉大人,你說我怎么就攤上這事看铆』斩Γ” “怎么了?”我有些...
    開封第一講書人閱讀 156,936評論 0 347
  • 文/不壞的土叔 我叫張陵性湿,是天一觀的道長纬傲。 經(jīng)常有香客問我,道長肤频,這世上最難降的妖魔是什么叹括? 我笑而不...
    開封第一講書人閱讀 56,427評論 1 283
  • 正文 為了忘掉前任,我火速辦了婚禮宵荒,結(jié)果婚禮上汁雷,老公的妹妹穿的比我還像新娘净嘀。我一直安慰自己,他們只是感情好侠讯,可當我...
    茶點故事閱讀 65,467評論 6 385
  • 文/花漫 我一把揭開白布挖藏。 她就那樣靜靜地躺著,像睡著了一般厢漩。 火紅的嫁衣襯著肌膚如雪膜眠。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,785評論 1 290
  • 那天溜嗜,我揣著相機與錄音宵膨,去河邊找鬼。 笑死炸宵,一個胖子當著我的面吹牛辟躏,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播土全,決...
    沈念sama閱讀 38,931評論 3 406
  • 文/蒼蘭香墨 我猛地睜開眼捎琐,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了裹匙?” 一聲冷哼從身側(cè)響起瑞凑,我...
    開封第一講書人閱讀 37,696評論 0 266
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎概页,沒想到半個月后拨黔,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,141評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡绰沥,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,483評論 2 327
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了贺待。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片徽曲。...
    茶點故事閱讀 38,625評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖麸塞,靈堂內(nèi)的尸體忽然破棺而出秃臣,到底是詐尸還是另有隱情,我是刑警寧澤哪工,帶...
    沈念sama閱讀 34,291評論 4 329
  • 正文 年R本政府宣布奥此,位于F島的核電站,受9級特大地震影響雁比,放射性物質(zhì)發(fā)生泄漏稚虎。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,892評論 3 312
  • 文/蒙蒙 一偎捎、第九天 我趴在偏房一處隱蔽的房頂上張望蠢终。 院中可真熱鬧序攘,春花似錦、人聲如沸寻拂。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,741評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽祭钉。三九已至瞄沙,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間慌核,已是汗流浹背距境。 一陣腳步聲響...
    開封第一講書人閱讀 31,977評論 1 265
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留遂铡,地道東北人肮疗。 一個月前我還...
    沈念sama閱讀 46,324評論 2 360
  • 正文 我出身青樓,卻偏偏與公主長得像扒接,于是被迫代替她去往敵國和親伪货。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 43,492評論 2 348