Java進階篇(2)—Optional類(預(yù)防空指針NPE)

JAVA && Spring && SpringBoot2.x — 學(xué)習目錄

Optional是JDK8引入的新特性摄闸,旨在解決空指針異常問題(后文采用NPE代指)。您可以將其看做為一個容器除盏,可以存放null值或非null的對象,Optional類目的是迫使用戶注意可能存在的NPE問題秋柄。關(guān)于它是否可以取代null值,可以參考下Java進階篇(3)—Optional(是否使用Optional來代替null)這篇文章。

Optional不推薦作為屬性撵孤、方法參數(shù)、集合元素竭望。因為完全可以使用null值來取代Optional邪码,并且Optional效率更加低下。甚至Optional類不能進行序列化咬清。它的使用場景便在于可以作為方法返回值闭专。來使得用戶注意存在的NPE問題。

Optional類更像一把雙刃劍旧烧,它可以盡量避免NPE對程序產(chǎn)生的潛在威脅影钉,而且鏈式操作,可以更加直觀掘剪、優(yōu)雅的對復(fù)雜對象進行處理平委。但是它并不能解決NPE問題,并且它作為一個引用夺谁,效率會更加的低下廉赔。我們在使用過程中愚墓,不能濫用Optional類。

1. Optional類如何構(gòu)建

既然Optional為存放對象的容器昂勉,我們首先考慮的如何將對象存入到Optional容器中。

方法 作用
Optional.of(obj) 它要求傳入的 obj 不能是 null 值的, 若是null則拋出NullPointerException 異常上了
Optional.empty() 創(chuàng)建一個空的Optional對象
Optional.ofNullable(obj) 智能的創(chuàng)建Optional實例扫腺,即傳入null則構(gòu)建Optional.empty()岗照,非null則構(gòu)建Optional.of(obj)

2. Option類如何使用

上面我們說到Optional并不能解決NPE問題,我們使用《1. Optional類如何構(gòu)建》方法笆环,將null值存入了Optional類攒至,使用get()方法獲取值時,會出現(xiàn)NoSuchElementException異常躁劣。實際上迫吐,他只是將NullPointerException異常進行了轉(zhuǎn)化。在調(diào)用get()方法時账忘,必須調(diào)用isPresent()方法志膀,去判斷對象是否存在。

2.1 Optional使用的錯誤案例

但是你以為這樣就是正確使用Optional的姿勢嗎鳖擒?
反例:若是我們以為這樣可以解決NullPointException問題溉浙,那么可真是有點大材小用了。

    @Test
    public void test2() {
        User user = null;
        //構(gòu)建Optional對象
        Optional<User> optional = Optional.ofNullable(user);
        //判斷Optional對象是否存在
        if (optional.isPresent()) {
            //業(yè)務(wù)操作
            System.out.println(optional.get());
        } else {
            //異常處理
            throw new RuntimeException("user不存在");
        }
    }

由上例我們可以看出:isPresent()方法實際上和obj!=null方法沒有任何的區(qū)別蒋荚,若是沒有isPresent()方法而單獨使用get()調(diào)用戳稽。也可能存在NoSuchElementException異常。

Optional類型不能作為字段或者方法參數(shù)期升,Optional是一個類庫方法惊奇,可明確表示可能無值請求下的返回類型。Optional類型不可序列化播赁。

使用Optional時颂郎,應(yīng)該盡量不調(diào)用Optional.get()方法,Optional.isPresent()更應(yīng)該被看做一個私有的方法(在Optional中其他方法借助該方法進行邏輯判斷)行拢,不應(yīng)該用其處理NullPointerException異常祖秒。

2.2 Optional正確的使用方式

在Optional中我們真正可以依賴的是除了isPresent()和get()方法的其他方法:我們可以通過下列的方法完成Optional鏈式操作。

在鏈式操作中舟奠,若出現(xiàn)NPE問題竭缝,或Filter返回false,那么便會執(zhí)行我們的異常方案沼瘫,即orElse()/ofNullable /orElseThrow方法抬纸,我們無需在為空指針異常而擔憂。我們便可以對該對象進行任意的操作耿戚。

//流類型轉(zhuǎn)化
public<U> Optional<U> map(Function<? super T, ? extends U> mapper)
//if...else
public T orElse(T other)
public T orElseGet(Supplier<? extends T> other)
//若存在的處理邏輯
public void ifPresent(Consumer<? super T> consumer)
//篩選
public Optional<T> filter(Predicate<? super T> predicate)
public<U> Optional<U> flatMap(Function<? super T, Optional<U>> mapper)
//若元素為null湿故,則拋出指定的異常
public <X extends Throwable> T orElseThrow(Supplier<? extends X> exceptionSupplier) throws X

1. orElse 存在即返回阿趁,無則返回默認值

return user.orElse(null);  //而不是 return user.isPresent() ? user.get() : null;
return user.orElse(UNKNOWN_USER);

2. orElseGet 存在即返回,無則由函數(shù)產(chǎn)生

    @Test
    public void test() {
        User user = new User().setUserName("ll");
        Optional<User> optional = Optional.ofNullable(user);
        //相當于三目表達式:return user.isPresent() ? user: getUser("aa");
        User user1 = optional.orElseGet(() -> getUser("aa"));
        System.out.println(user);
    }
    private User getUser(String id) {
        return new User();
    }

3. orElseThrow 存在即返回坛猪,無則拋出異常

    @Test
    public void test5() {
        User user = null;
        Optional<User> optional = Optional.ofNullable(user);
        User user1 = optional.orElseThrow(() -> new RuntimeException("user不能為null"));
    }

4. ifPresent 若對象不為null脖阵,則進行操作

    @Test
    public void test3() {
        User user = new User().setUserName("ll");
        Optional<User> optional = Optional.ofNullable(user);
        optional.ifPresent(o -> {
            Integer id = o.getId();
        });
      //而不要下邊那樣。
      if (optional.isPresent()) {
          System.out.println(optional.get());
      }
    }

5. map 若對象存在墅茉,則進行轉(zhuǎn)換

    @Test
    public void test4() {

        User user = null;
        Optional<User> optional = Optional.ofNullable(user);
        //map返回類型依舊為Optional類型
        Optional<Integer> integer = optional.map(u -> u.getId());
        //若使用orElse()命黔,只要出現(xiàn)null值,那么便轉(zhuǎn)換為1
        Integer integer1 = optional.map(u -> u.getId()).orElse(1);
        System.out.println(integer1);
    }

    //相當于下面的使用
    @Test
    public void test41() {
        User user = null;
        if (user != null && user.getId() != null) {
            System.out.println(user.getId());
        } else {
            System.out.println(1);
        }
    }

6. filter過濾條件就斤,不符合條件悍募,則返回null

    @Test
    public void test6() {
        User user = new User().setUserName("aa").setUserAge(20);

        Optional<User> user1 = Optional.of(user);
        //中間操作,不符合條件則為null
        User user2 = user1.filter(u -> "ab".equals(u.getUserName())).orElseThrow(RuntimeException::new);
    }

2.3 實戰(zhàn)

校驗user.getAge()是否0<age<=100洋机,但是要判斷是否存在NPE坠宴。

    /**
     * 校驗user.getAge()是否0<age<=100,但是要判斷是否存在NPE绷旗。
     */
    private static void testMapElse() {
        User user = new User();

        Boolean flag = Optional.ofNullable(user)
                .map(u -> u.getAge())
                .map(u -> (u > 0 && u <= 100))
                .orElse(false);

        System.out.println(flag);
    }

2.4 封裝工具類

public class OptionalUtils {

    /**
     * 若是null喜鼓,則傳入默認值
     */
    public static <T> T nullToDefault(T value, T defaultValue) {
        return Optional.ofNullable(value).orElse(defaultValue);
    }

    /**
     * 校驗參數(shù)的時候,首先得去判斷是否為空衔肢,太麻煩了颠通,故抽取出來。先判斷若是參數(shù)為null膀懈,則直接返回false顿锰。
     * 否則的話,則去執(zhí)行filter的過濾操作
     */
    public static <T> boolean checkNoNPE(T value, Function<T, Boolean> filter) {
        return Optional.ofNullable(value).map(filter).orElse(false);
    }

    /**
     * 校驗參數(shù)是否在指定范圍之內(nèi)启搂。
     */
    public static boolean between(Double target, double begin, double end) {
        return Optional.ofNullable(target)
                .map(t -> (t >= begin && t < end))
                .orElse(false);
    }

    /**
     * 三目表達式——supplier?a:b
     */
    public static <T> T conditionalAB(Supplier<Boolean> supplier, T a, T b) {
        return nullToDefault(supplier.get(), false) ? a : b;
    }

    /**
     * 取代三目表達式
     */
    public static <T> T conditionalAB(boolean result, T a, T b) {
        return result ? a : b;
    }

}

3. lamda與Optional搭配(Guava提供)

使用jdk的lamda表達式解析集合時硼控,在最后時可以轉(zhuǎn)化為Optional。

MoreCollectors的API使用:

<dependency>
    <groupId>com.google.guava</groupId>
    <artifactId>guava</artifactId>
    <version>31.0.1-jre</version>
</dependency>
    public static void main(String[] args) {
        //集合
        List<User> users = new ArrayList<>();
        User u1 = new User();
        u1.setAge(110);
        User u2 = new User();
        u2.setAge(10);

        users.add(u1);
        users.add(u2);

        //必須有l(wèi)imit(1)胳赌,將元素設(shè)置為Optional牢撼。在使用之前的方法
        Optional<User> collect = users.stream().limit(1).collect(MoreCollectors.toOptional());
        System.out.println(collect.orElse(null));
    }

find的相關(guān)API:

    public static void main(String[] args) {
        List<User> users = new ArrayList<>();
        User u1 = new User();
        u1.setAge(110);
        User u2 = new User();
        u2.setAge(10);
        users.add(u1);
        users.add(u2);
        /**
         * findAny返回值
         */
        Optional<String> s = users.stream().findAny().map(User::getGj);
        /**
         * findFirst返回值
         */
        Optional<String> s1 = users.stream().findFirst().map(User::getGj);
    }

參考

使用 Java 8 Optional 的正確姿勢

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市疑苫,隨后出現(xiàn)的幾起案子熏版,更是在濱河造成了極大的恐慌,老刑警劉巖捍掺,帶你破解...
    沈念sama閱讀 219,427評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件撼短,死亡現(xiàn)場離奇詭異,居然都是意外死亡挺勿,警方通過查閱死者的電腦和手機曲横,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,551評論 3 395
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來进宝,“玉大人紊扬,你說我怎么就攤上這事∑牵” “怎么了熙参?”我有些...
    開封第一講書人閱讀 165,747評論 0 356
  • 文/不壞的土叔 我叫張陵艳吠,是天一觀的道長。 經(jīng)常有香客問我孽椰,道長讲竿,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,939評論 1 295
  • 正文 為了忘掉前任弄屡,我火速辦了婚禮,結(jié)果婚禮上鞋诗,老公的妹妹穿的比我還像新娘膀捷。我一直安慰自己,他們只是感情好削彬,可當我...
    茶點故事閱讀 67,955評論 6 392
  • 文/花漫 我一把揭開白布全庸。 她就那樣靜靜地躺著,像睡著了一般融痛。 火紅的嫁衣襯著肌膚如雪壶笼。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,737評論 1 305
  • 那天雁刷,我揣著相機與錄音覆劈,去河邊找鬼。 笑死沛励,一個胖子當著我的面吹牛责语,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播目派,決...
    沈念sama閱讀 40,448評論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼坤候,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了企蹭?” 一聲冷哼從身側(cè)響起白筹,我...
    開封第一講書人閱讀 39,352評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎谅摄,沒想到半個月后徒河,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,834評論 1 317
  • 正文 獨居荒郊野嶺守林人離奇死亡送漠,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,992評論 3 338
  • 正文 我和宋清朗相戀三年虚青,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片螺男。...
    茶點故事閱讀 40,133評論 1 351
  • 序言:一個原本活蹦亂跳的男人離奇死亡棒厘,死狀恐怖纵穿,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情奢人,我是刑警寧澤谓媒,帶...
    沈念sama閱讀 35,815評論 5 346
  • 正文 年R本政府宣布,位于F島的核電站何乎,受9級特大地震影響句惯,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜支救,卻給世界環(huán)境...
    茶點故事閱讀 41,477評論 3 331
  • 文/蒙蒙 一抢野、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧各墨,春花似錦指孤、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,022評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至黎做,卻和暖如春叉跛,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背蒸殿。 一陣腳步聲響...
    開封第一講書人閱讀 33,147評論 1 272
  • 我被黑心中介騙來泰國打工筷厘, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人宏所。 一個月前我還...
    沈念sama閱讀 48,398評論 3 373
  • 正文 我出身青樓敞掘,卻偏偏與公主長得像,于是被迫代替她去往敵國和親楣铁。 傳聞我的和親對象是個殘疾皇子玖雁,可洞房花燭夜當晚...
    茶點故事閱讀 45,077評論 2 355

推薦閱讀更多精彩內(nèi)容