給女朋友講解什么是Optional【JDK 8特性】

前言

只有光頭才能變強

前兩天帶女朋友去圖書館了煎楣,隨手就給她來了一本《與孩子一起學(xué)編程》的書,于是今天就給女朋友講解一下什么是Optional類。

  • 至于她能不能看懂鳞疲,那肯定是看不懂的秧骑。(學(xué)到變量/for循環(huán)的女人怎么能看懂呢)

不知道大家還記得上一篇《阿里巴巴 Java開發(fā)手冊》讀后感不版确,當(dāng)時閱讀到空指針異常(NPE)時,書上提到JDK 8有個Optional類供我們使用乎折,該類可以盡可能地防止出現(xiàn)空指針異常(NPE)绒疗。

文本力求簡單講清每個知識點,希望大家看完能有所收獲

一骂澄、基礎(chǔ)鋪墊

我們都知道JDK 8最重要的新特性是Lambda表達(dá)式吓蘑,這個可以讓我們簡化非常多的代碼編寫,不知道大家會使用了沒有坟冲。這里我簡單跟大家來回顧一下~

1.1Lambda簡化代碼例子

下面就以幾個例子來看看Lambda表達(dá)式是怎么簡化我們代碼的編寫的磨镶。

首先我們來看看創(chuàng)建線程


public static void main(String[] args) {
    // 用匿名內(nèi)部類的方式來創(chuàng)建線程
    new Thread(new Runnable() {
        @Override
        public void run() {
            System.out.println("公眾號:Java3y---回復(fù)1進(jìn)群交流");
        }
    });

    // 使用Lambda來創(chuàng)建線程
    new Thread(() -> System.out.println("公眾號:Java3y---回復(fù)1進(jìn)群交流"));
}

再來看看遍歷Map集合:



public static void main(String[] args) {
    Map<String, String> hashMap = new HashMap<>();
    hashMap.put("公眾號", "Java3y");
    hashMap.put("交流群", "回復(fù)1");

    // 使用增強for的方式來遍歷hashMap
    for (Map.Entry<String, String> entry : hashMap.entrySet()) {
        System.out.println(entry.getKey()+":"+entry.getValue());
    }

    // 使用Lambda表達(dá)式的方式來遍歷hashMap
    hashMap.forEach((s, s2) -> System.out.println(s + ":" + s2));
}

在List中刪除某個元素


public static void main(String[] args) {

    List<String> list = new ArrayList<>();
    list.add("Java3y");
    list.add("3y");
    list.add("光頭");
    list.add("帥哥");
    
    // 傳統(tǒng)的方式刪除"光頭"的元素
    ListIterator<String> iterator = list.listIterator();
    while (iterator.hasNext()) {
        if ("光頭".equals(iterator.next())) {
            iterator.remove();
        }
    }

    // Lambda方式刪除"光頭"的元素
    list.removeIf(s -> "光頭".equals(s));
    
    // 使用Lambda遍歷List集合
    list.forEach(s -> System.out.println(s));
}

從上面的例子我們可以看出,Lambda表達(dá)式的確是可以幫我們簡化代碼的健提。

1.1函數(shù)式接口

使用Lambda表達(dá)式琳猫,其實都是建立在函數(shù)式接口上的。我們看看上面的代碼的接口:

創(chuàng)建多線程的Runnable接口:


@FunctionalInterface
public interface Runnable {
    public abstract void run();
}

遍歷HashMap的BiConsumer接口:


@FunctionalInterface
public interface BiConsumer<T, U> {
    void accept(T t, U u);
    default BiConsumer<T, U> andThen(BiConsumer<? super T, ? super U> after) {
        Objects.requireNonNull(after);
        return (l, r) -> {
            accept(l, r);
            after.accept(l, r);
        };
    }
}

在List中刪除元素的Predicate接口:


@FunctionalInterface
public interface Predicate<T> {

    boolean test(T t);

    default Predicate<T> and(Predicate<? super T> other) {
        Objects.requireNonNull(other);
        return (t) -> test(t) && other.test(t);
    }
    default Predicate<T> negate() {
        return (t) -> !test(t);
    }
    default Predicate<T> or(Predicate<? super T> other) {
        Objects.requireNonNull(other);
        return (t) -> test(t) || other.test(t);
    }
    static <T> Predicate<T> isEqual(Object targetRef) {
        return (null == targetRef)
                ? Objects::isNull
                : object -> targetRef.equals(object);
    }
}

函數(shù)式接口的特點:由@FunctionalInterface注解標(biāo)識私痹,接口有且僅有一個抽象方法脐嫂!

1.2Lambda簡單講解

或許我們一開始看到Lambda的時候统刮,發(fā)現(xiàn)Lambda表達(dá)式的語法有點奇葩,甚至有點看不懂账千。沒事侥蒙,這里3y給大家用圖的形式畫一畫:

Lambda表達(dá)式組成

以Runnable接口來舉例:

Lambda表達(dá)式很簡單!

再不濟匀奏,我們在用IDE的時候鞭衩,可以提示出Lambda表達(dá)式的語法的,這樣可以幫我們快速上手Lambda表達(dá)式:

IDEA提示Lambda表達(dá)式

說白了娃善,我們使用Lambda表達(dá)式的架子是這樣的()->{}醋旦,具體的時候看看函數(shù)式接口的抽象方法要求就可以了,再不濟就使用IDE智能提示会放。

1.3泛型回顧

比如說public<U> Optional<U> map(Function<? super T, ? extends U> mapper)這個聲明饲齐,你看懂了嗎?


// 接口
@FunctionalInterface
public interface Function<T, R> {
    R apply(T t);
}

在泛型的上限和下限中有一個原則:PECS(Producer Extends Consumer Super)

  • 帶有子類限定的可以從泛型讀取【也就是--->(? extend T)】-------->Producer Extends
  • 帶有超類限定的可以從泛型寫入【也就是--->(? super T)】-------->Consumer Super

解析:傳入的參數(shù)是泛型 T 或者其父類咧最,返回值是U或其子類捂人。

具體可參考:

二、Optional類

一句話介紹Optional類:使用JDK8的Optional類來防止NPE(空指針異常)問題矢沿。

接下來我們看看文檔是怎么說的:

A container object which may or may not contain a non-null value.Additional methods that depend on the presence or absence of a contained value are provided

它是一個容器滥搭,裝載著非NULL元素(或者沒有裝載元素),提供了一系列的方法供我們判斷該容器里的對象是否存在(以及后續(xù)的操作)捣鲸。

Optional類的方法結(jié)構(gòu)圖:

Optional類的方法結(jié)構(gòu)圖

2.1創(chuàng)建Optional容器

我們先來看看Optional的屬性以及創(chuàng)建Optional容器的方法:


    // 1瑟匆、創(chuàng)建出一個Optional容器,容器里邊并沒有裝載著對象
    private static final Optional<?> EMPTY = new Optional<>();

    // 2栽惶、代表著容器中的對象
    private final T value;

    // 3愁溜、私有構(gòu)造方法
    private Optional() {
        this.value = null;
    }

    // 4、得到一個Optional容器外厂,Optional沒有裝載著對象
    public static<T> Optional<T> empty() {
        @SuppressWarnings("unchecked")
        Optional<T> t = (Optional<T>) EMPTY;
        return t;
    }

    // 5冕象、私有構(gòu)造方法(帶參數(shù)),參數(shù)就是具體的要裝載的對象汁蝶,如果傳進(jìn)來的對象為null渐扮,拋出異常
    private Optional(T value) {
        this.value = Objects.requireNonNull(value);
    }

    // 5.1、如果傳進(jìn)來的對象為null掖棉,拋出異常
    public static <T> T requireNonNull(T obj) {
        if (obj == null)
            throw new NullPointerException();
        return obj;
    }


    // 6墓律、創(chuàng)建出Optional容器,并將對象(value)裝載到Optional容器中幔亥。
    // 傳入的value如果為null耻讽,拋出異常(調(diào)用的是Optional(T value)方法)
    public static <T> Optional<T> of(T value) {
        return new Optional<>(value);
    }

    // 創(chuàng)建出Optional容器,并將對象(value)裝載到Optional容器中紫谷。
    // 傳入的value可以為null齐饮,如果為null,返回一個沒有裝載對象的Optional對象
    public static <T> Optional<T> ofNullable(T value) {
        return value == null ? empty() : of(value);
    }

所以可以得出創(chuàng)建Optional容器有兩種方式:

  • 調(diào)用ofNullable()方法笤昨,傳入的對象可以為null
  • 調(diào)用of()方法祖驱,傳入的對象不可以為null,否則拋出NullPointerException

下面我們簡單就可以看看用法了:

現(xiàn)在我有一個User對象瞒窒,這里用到了Lombok捺僻,有興趣的同學(xué)可去學(xué)學(xué)了解一下:兩個月的Java實習(xí)結(jié)束,繼續(xù)努力


import lombok.Data;
@Data
public class User {

    private Integer id;
    private String name;
    private Short age;
}

測試:


public static void main(String[] args) {

    User user = new User();
    User user1 = null;

    // 傳遞進(jìn)去的對象不可以為null崇裁,如果為null則拋出異常
    Optional<User> op1 = Optional.of(user1);

    // 傳遞進(jìn)去的對象可以為null匕坯,如果為null則返回一個沒有裝載對象的Optional容器
    Optional<User> op2 = Optional.ofNullable(user);
}
結(jié)果

2.2Optional容器簡單的方法


// 得到容器中的對象,如果為null就拋出異常
public T get() {
    if (value == null) {
        throw new NoSuchElementException("No value present");
    }
    return value;
}

// 判斷容器中的對象是否為null
public boolean isPresent() {
    return value != null;
}

// 如果容器中的對象存在拔稳,則返回葛峻。否則返回傳遞進(jìn)來的參數(shù)
public T orElse(T other) {
    return value != null ? value : other;
}

這三個方法是Optional類比較常用的方法,并且是最簡單的巴比。(因為參數(shù)不是函數(shù)式接口)

下面我們繼續(xù)看看用法:


public static void main(String[] args) {

        User user = new User();
        User user1 = null;

        Optional<User> op1 = Optional.ofNullable(user);
        System.out.println(op1.isPresent());
        System.out.println(op1.get());
        System.out.println(op1.orElse(user1));

    }

結(jié)果很明顯术奖,因為我們的user是不為null的:

結(jié)果

我們調(diào)換一下順序看看:


public static void main(String[] args) {

    User user = new User();
    User user1 = null;

    Optional<User> op1 = Optional.ofNullable(user1);
    System.out.println(op1.isPresent());
    System.out.println(op1.orElse(user));
    System.out.println(op1.get());

}
結(jié)果

2.3Optional容器進(jìn)階用法

當(dāng)然了,我們到目前為止看起來Optional類好像就這么一回事了轻绞,這樣代碼寫起來還不如我自己判斷null呢...

我們對比一下:

對比

我們可以發(fā)現(xiàn)采记,手動判斷是否為null好像還更方便簡潔一點呢。

所以政勃,我們帶函數(shù)式接口的方法登場了唧龄!

2.3.1ifPresent方法

首先來看看ifPresent(Consumer<? super T> consumer)方法



public void ifPresent(Consumer<? super T> consumer) {
    if (value != null)
        consumer.accept(value);
}

@FunctionalInterface
public interface Consumer<T> {
    void accept(T t);
}

如果容器中的對象存在,則調(diào)用accept方法奸远,比如說:


public static void main(String[] args) {

    User user = new User();
    user.setName("Java3y");
    test(user);
}

public static void test(User user) {

    Optional<User> optional = Optional.ofNullable(user);

    // 如果存在user既棺,則打印user的name
    optional.ifPresent((value) -> System.out.println(value.getName()));

    // 舊寫法
    if (user != null) {
        System.out.println(user.getName());
    }
}

2.3.2orElseGet和orElseThrow方法

直接看源碼:


// 如果對象存在,則直接返回懒叛,否則返回由Supplier接口的實現(xiàn)用來生成默認(rèn)值
public T orElseGet(Supplier<? extends T> other) {
    return value != null ? value : other.get();
}


@FunctionalInterface
public interface Supplier<T> {
    T get();
}


// 如果存在援制,則返回。否則拋出supplier接口創(chuàng)建的異常
public <X extends Throwable> T orElseThrow(Supplier<? extends X> exceptionSupplier) throws X {
    if (value != null) {
        return value;
    } else {
        throw exceptionSupplier.get();
    }
}

例子:


public static void main(String[] args) {

    User user = new User();
    user.setName("Java3y");
    test(user);
}

public static void test(User user) {

    Optional<User> optional = Optional.ofNullable(user);

    // 如果存在user芍瑞,則直接返回晨仑,否則創(chuàng)建出一個新的User對象
    User user1 = optional.orElseGet(() -> new User());
    
    // 舊寫法
    if (user != null) {
        user = new User();
    }
}

總的來說跟我們上面所講的orElse()差不多,只不過它可以通過Supplier接口的實現(xiàn)來生成默認(rèn)值拆檬。

2.3.3filter方法

直接看源碼:


// 如果容器中的對象存在洪己,并且符合過濾條件,返回裝載對象的Optional容器竟贯,否則返回一個空的Optional容器
public Optional<T> filter(Predicate<? super T> predicate) {
    Objects.requireNonNull(predicate);
    if (!isPresent())
        return this;
    else
        return predicate.test(value) ? this : empty();
}


// 接口
@FunctionalInterface
public interface Predicate<T> {

    boolean test(T t);
}

返回Optional對象我們就可以實現(xiàn)鏈?zhǔn)秸{(diào)用了答捕!

例子:


public static void test(User user) {

    Optional<User> optional = Optional.ofNullable(user);

    // 如果容器中的對象存在,并且符合過濾條件屑那,返回裝載對象的Optional容器拱镐,否則返回一個空的Optional容器
    optional.filter((value) -> "Java3y".equals(value.getName()));
}

2.3.4map方法

直接看源碼:


// 如果容器的對象存在艘款,則對其執(zhí)行調(diào)用mapping函數(shù)得到返回值。然后創(chuàng)建包含mapping返回值的Optional沃琅,否則返回空Optional哗咆。
public<U> Optional<U> map(Function<? super T, ? extends U> mapper) {
    Objects.requireNonNull(mapper);
    if (!isPresent())
        return empty();
    else {
        return Optional.ofNullable(mapper.apply(value));
    }
}


// 接口
@FunctionalInterface
public interface Function<T, R> {
    R apply(T t);
}

例子:


public static void test(User user) {

    Optional<User> optional = Optional.ofNullable(user);

    // 如果容器的對象存在,則對其執(zhí)行調(diào)用mapping函數(shù)得到返回值益眉。然后創(chuàng)建包含mapping返回值的Optional晌柬,否則返回空Optional。
    optional.map(user1 -> user1.getName()).orElse("Unknown");
}

// 上面一句代碼對應(yīng)著最開始的老寫法:

public String tradition(User user) {
    if (user != null) {
        return user.getName();
    }else{
        return "Unknown";
    }
}

2.3.5flatMap方法

直接看源碼:


// flatMap方法與map方法類似郭脂,區(qū)別在于apply函數(shù)的返回值不同年碘。map方法的apply函數(shù)返回值是? extends U,而flatMap方法的apply函數(shù)返回值必須是Optional
public<U> Optional<U> flatMap(Function<? super T, Optional<U>> mapper) {
    Objects.requireNonNull(mapper);
    if (!isPresent())
        return empty();
    else {
        return Objects.requireNonNull(mapper.apply(value));
    }
}

2.3.6總結(jié)

再來感受一下Optional的魅力


public static void main(String[] args) {
    User user = new User();
    user.setName("Java3y");
    System.out.println(test(user));
}

// 以前的代碼v1
public static String test2(User user) {
    if (user != null) {
        String name = user.getName();
        if (name != null) {
            return name.toUpperCase();
        } else {
            return null;
        }
    } else {
        return null;
    }
}

// 以前的代碼v2
public static String test3(User user) {
    if (user != null && user.getName() != null) {
        return user.getName().toUpperCase();
    } else {
        return null;
    }
}

// 現(xiàn)在的代碼
public static String test(User user) {
    return Optional.ofNullable(user)
            .map(user1 -> user1.getName())
            .map(s -> s.toUpperCase()).orElse(null);
}

Optional總結(jié):

filter展鸡,map或flatMap一個函數(shù)屿衅,函數(shù)的參數(shù)拿到的值一定不是null。所以我們通過filter莹弊,map 和 flatMap之類的函數(shù)可以將其安全的進(jìn)行變換傲诵,最后通過orElse系列,get箱硕,isPresent 和 ifPresent將其中的值提取出來拴竹。

其實吧,用Optional類也沒有簡化很多的代碼剧罩,只是把NPE異常通過各種方法隱藏起來(包裝了一層)栓拜。通過Lambda表達(dá)式可以讓我們處理起來更加"優(yōu)雅"一些。

三惠昔、最后

之前在初學(xué)的時候沒在意JDK8的特性幕与,其實JDK更新很多時候都能給我們帶來不少好處的(簡化代碼編寫,提高性能等等)镇防,所以作為一名Java程序員啦鸣,還是得多學(xué)學(xué)新特性。(話說JDK9該類又有新特性了...)

如果你要評論“醒醒吧来氧,程序員哪來的女朋友”诫给,“我尿黃,讓我來”之類的話啦扬,我建議你是不是好好反省一下自己中狂,為什么別的程序員都有女朋友,就你沒有扑毡,是不是自己技術(shù)不過關(guān)了胃榕?通過“工廠”找一個有那么難嗎?再不濟也能自己new一個出來啊瞄摊。

當(dāng)然了勋又,我的女朋友是現(xiàn)實存在的苦掘。

參考資料:

如果你覺得我寫得還不錯,了解一下:

  • 堅持原創(chuàng)的技術(shù)公眾號:Java3y楔壤『追龋回復(fù) 1 加入Java交流群
  • 文章的目錄導(dǎo)航(精美腦圖+海量視頻資源):https://github.com/ZhongFuCheng3y/3y
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市挺邀,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌跳座,老刑警劉巖端铛,帶你破解...
    沈念sama閱讀 222,000評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異疲眷,居然都是意外死亡禾蚕,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,745評論 3 399
  • 文/潘曉璐 我一進(jìn)店門狂丝,熙熙樓的掌柜王于貴愁眉苦臉地迎上來换淆,“玉大人,你說我怎么就攤上這事几颜”妒裕” “怎么了?”我有些...
    開封第一講書人閱讀 168,561評論 0 360
  • 文/不壞的土叔 我叫張陵蛋哭,是天一觀的道長县习。 經(jīng)常有香客問我,道長谆趾,這世上最難降的妖魔是什么躁愿? 我笑而不...
    開封第一講書人閱讀 59,782評論 1 298
  • 正文 為了忘掉前任,我火速辦了婚禮沪蓬,結(jié)果婚禮上彤钟,老公的妹妹穿的比我還像新娘。我一直安慰自己跷叉,他們只是感情好逸雹,可當(dāng)我...
    茶點故事閱讀 68,798評論 6 397
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著云挟,像睡著了一般峡眶。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上植锉,一...
    開封第一講書人閱讀 52,394評論 1 310
  • 那天辫樱,我揣著相機與錄音,去河邊找鬼俊庇。 笑死狮暑,一個胖子當(dāng)著我的面吹牛鸡挠,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播搬男,決...
    沈念sama閱讀 40,952評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼拣展,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了缔逛?” 一聲冷哼從身側(cè)響起备埃,我...
    開封第一講書人閱讀 39,852評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎褐奴,沒想到半個月后按脚,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,409評論 1 318
  • 正文 獨居荒郊野嶺守林人離奇死亡敦冬,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,483評論 3 341
  • 正文 我和宋清朗相戀三年辅搬,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片脖旱。...
    茶點故事閱讀 40,615評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡堪遂,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出萌庆,到底是詐尸還是另有隱情溶褪,我是刑警寧澤,帶...
    沈念sama閱讀 36,303評論 5 350
  • 正文 年R本政府宣布践险,位于F島的核電站竿滨,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏捏境。R本人自食惡果不足惜于游,卻給世界環(huán)境...
    茶點故事閱讀 41,979評論 3 334
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望垫言。 院中可真熱鬧贰剥,春花似錦、人聲如沸筷频。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,470評論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽凛捏。三九已至担忧,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間坯癣,已是汗流浹背瓶盛。 一陣腳步聲響...
    開封第一講書人閱讀 33,571評論 1 272
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人惩猫。 一個月前我還...
    沈念sama閱讀 49,041評論 3 377
  • 正文 我出身青樓芝硬,卻偏偏與公主長得像,于是被迫代替她去往敵國和親轧房。 傳聞我的和親對象是個殘疾皇子拌阴,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,630評論 2 359

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