Java 8 Optional入門實(shí)戰(zhàn)

1. 簡(jiǎn)介

本文簡(jiǎn)要介紹一下Java 8 引入的 Optional 類橘沥。引入Optional 類的主要目的是為使用可選值代替 null 提供類型級(jí)解決方案臀晃。如果觉渴,你想知道為什么需要更深入的了解和使用 Optional 類,可以參考甲骨文官方文章徽惋。

Optionaljava.util.package 的一部分案淋,為了能夠使用,需要導(dǎo)入Optional

    import java.util.Optional;

2. 創(chuàng)建 Optional 對(duì)象

有多種方式可以創(chuàng)建 Optional 對(duì)象险绘,可以使用下面的方法創(chuàng)建一個(gè)空的 Optianal對(duì)象:

    @Test
    public void test_createsEmptyOptionalObject() throws Exception {
        Optional<String> empty = Optional.empty();
        assertFalse(empty.isPresent());
    }

可以使用 isPresent API 來檢查 Optional 對(duì)象是否有封裝的值踢京,當(dāng)且僅當(dāng) * Optional* 封裝了非 null 值時(shí),API才返回 true宦棺。

還可以使用 Optional 提供了靜態(tài)方法創(chuàng)建 Optional 對(duì)象:

    @Test
    public void test_createOptionalObjectWithStaticMethod() throws Exception {
        String val = "not null";
        Optional<String> hasVal = Optional.of(val);
        assertTrue(hasVal.isPresent());
    }

如果 Optional 對(duì)象有封裝的值(非 null )瓣距,可以對(duì)封裝的值進(jìn)行處理:

    @Test
    public void test_processOptionalValue() throws Exception {
        String val = "not null";
        Optional<String> hasVal = Optional.of(val);
        System.out.println(hasVal.toString());
        assertEquals("Optional[not null]", hasVal.toString());
    }

當(dāng)使用 Optional 提供的靜態(tài)方法 of 創(chuàng)建 Optional 對(duì)象時(shí),方法的參數(shù)不能null代咸,否則蹈丸,方法會(huì)拋出 NullPointerException

    @Test(expected = NullPointerException.class)
    public void test_throwNullPointerException() throws Exception {
        String val = null;
        Optional<String> hasVal = Optional.of(val);
    }

如果構(gòu)建 Optional 對(duì)象時(shí)可以傳入 null 參數(shù),可以使用 ofNullable 方法代替of

    @Test
    public void test_passNullParamNoException() throws Exception {
        String val = null;
        Optional<String> hasVal = Optional.ofNullable(val);
        assertFalse(hasVal.isPresent());
    }

使用 ofNullable 方法創(chuàng)建 Optional 對(duì)象時(shí)呐芥,如果傳入一個(gè) null 參數(shù)逻杖,方法不會(huì)拋出異常,而是返回一個(gè)空的 Optional 對(duì)象思瘟,和使用 Optional.empty API 創(chuàng)建的一樣荸百。

3. 檢查值是否存在

當(dāng)?shù)玫揭粋€(gè)從其他方法返回或自己創(chuàng)建的 Optional 對(duì)象后,可以使用isPresent API 檢查 Optional 對(duì)象是否有封裝值:

    @Test
    public void test_checkValuePresentOrNot() throws Exception {
        Optional<String> opt = Optional.of("has value");
        assertTrue(opt.isPresent());

        opt = Optional.ofNullable(null);
        assertFalse(opt.isPresent());
    }

當(dāng)且僅當(dāng)Optional 對(duì)象封裝一個(gè)非空值時(shí)滨攻,isPresent API才返回true够话。

在Java 11 中可以使用 isEmpty API 完成相反的工作:

    @Test
    public void test_checkValuePresentOrNotJava11() throws Exception {
        Optional<String> opt = Optional.of("has value");
        assertFalse(opt.isEmpty());

        opt = Optional.ofNullable(null);
        assertTrue(opt.isEmpty());
    }

當(dāng)且僅當(dāng) Optional 對(duì)象封裝的值為 null 時(shí),isEmpty 返回true铡买,其他情況返回false更鲁。

4. 使用 ifPresent() 進(jìn)行條件處理

ifPresent API 允許我們?cè)?Optional 對(duì)象封裝的值非空時(shí)執(zhí)行一些代碼,在沒有Optional 之前奇钞,最常用的方法是使用 if 語(yǔ)句進(jìn)行判斷澡为,結(jié)果為真時(shí)執(zhí)行代碼邏輯:

    if(name != null){
        System.out.println(name.length);
    }

這段代碼在執(zhí)行其他代碼之前先檢查 name 變量是否為 null。冗長(zhǎng)并不是這種方法的唯一問題一景埃,這種方法固有很多潛在的bug媒至。

在習(xí)慣了這種方法之后,很容易忘記在代碼的某些部分執(zhí)行空檢查谷徙。如果 null 值進(jìn)入該代碼拒啰,可能會(huì)在運(yùn)行時(shí)導(dǎo)致 NullPointerException 異常。 當(dāng)程序因輸入問題而失敗時(shí)完慧,通常是編碼不夠健壯導(dǎo)致谋旦,也是代碼實(shí)踐不好的結(jié)果。

作為強(qiáng)制執(zhí)行良好編程實(shí)踐的一種方式,Optional 可以明確地處理 null册着。 在典型的函數(shù)式編程風(fēng)格中拴孤,我們可以對(duì)實(shí)際存在的對(duì)象執(zhí)行操作,使用Java 8重構(gòu)上面的代碼如下:

    @Test
    public void doSomeThingWhenExist()  throws Exception {
        Optional<String> opt = Optional.of("baeldung");
        opt.ifPresent(name -> System.out.println(name.length()));
    }

5. 使用 orElse 獲取封裝的值

orElse API 用于從 Optional 實(shí)例中獲取封裝的值甲捏,orElse 的唯一參數(shù)作為Optional 無封裝值時(shí)的默認(rèn)值演熟,這點(diǎn)類似 System.getProperty API。如果司顿,Optional 有封裝值 orElse API返回 Optional 封裝的值芒粹,否則返回參數(shù)的值。

    @Test
    public void test_getValueUseorElse() throws Exception {
        Optional<String> hasVal = Optional.of("Hello");
        String val = hasVal.orElse("no value");
        assertEquals("Hello", val);

        Optional<String> noVal = Optional.empty();
        String defaultVal = noVal.orElse("default");
        assertEquals("default", defaultVal);
    }

6. 使用 orElseGet 獲封裝的值

orElseGet API 功能和 orElse 類似大溜,兩者的不同之處在于 orElseGet 的參數(shù)為一個(gè) Supplier 實(shí)例化漆,當(dāng) Optional 對(duì)象無封裝值時(shí),orElseGet 調(diào)用 Supplier 實(shí)例的 get 方法猎提,并將返回值作為 orElseGet 的返回值获三。

    @Test
    public void test_getValueUseorElseget() throws Exception {
        Optional<String> hasVal = Optional.of("Hello");
        String val = hasVal.orElseGet(() -> "no value");
        assertEquals("Hello", val);

        Optional<String> noVal = Optional.empty();
        String defaultVal = noVal.orElseGet(() -> "default");
        assertEquals("default", defaultVal);
    }

7. orElseorElseGet 的區(qū)別

Optional 對(duì)象無封裝值時(shí),orElseorElseGet 并無本質(zhì)上的區(qū)別锨苏,兩個(gè)API 都返回各自的默認(rèn)值疙教。但是,當(dāng) Optional 對(duì)象有封裝值時(shí)兩者有很大的區(qū)別伞租,而且兩者在性能上的差異也十分明顯贞谓。一句話總結(jié)兩者的差異就是:orElse 會(huì)觸發(fā)獲取默認(rèn)值的動(dòng)作,盡管并不需要葵诈。為了更加形象的說明裸弦,這里提供一個(gè)方法用于獲取默認(rèn)值,方法中使用 sleep 模擬這是一個(gè)耗時(shí)的操作:

    private String getDefaultValue() {
        System.out.println("enter method get default value");
        try {
            TimeUnit.SECONDS.sleep(5);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return "default value";
    }

創(chuàng)建一個(gè)非空的 Optional 對(duì)象作喘,分別調(diào)用 orElseorElseGet 方法理疙,觀察兩者行為上的差異:

    @Test
    public void test_differenceorElseAndorElseGet() throws Exception {
        Optional<String> hasVal = Optional.of("value");
        System.out.println("enter orElse method");
        String var0 = hasVal.orElse(getDefaultValue());

        System.out.println("enter orElseGet method");
        String var1 = hasVal.orElseGet(this::getDefaultValue);
    }

上面代碼的輸出結(jié)果如下:

enter orElse method
enter method get default value
enter orElseGet method

從輸出結(jié)果可以非常清晰的看出兩個(gè)API之間的差異,為了更好的性能泞坦,在編碼中優(yōu)先使用 orElseGet API 獲取 Optional 的值窖贤。。

8. 使用 orElseThrow 拋出異常

orElseThroworElseorElseGet API類似贰锁,orElseThrow 提供了一種在Optional 為空時(shí)的處理方法-拋異常而不是返回默認(rèn)值赃梧。

    @Test(expected = IllegalArgumentException.class)
    public void test_throwsExecption() {
        String nullName = null;
        String name = Optional.ofNullable(nullName).orElseThrow(
                IllegalArgumentException::new);
    }

9. 使用 get() 獲取值

get 是獲取 * Optional* 值的最后方法(不是一個(gè)好方法):

    @Test
    public void test_getValueUseGet() {
        Optional<String> opt = Optional.of("value");
        String name = opt.get();
        assertEquals("value", name);
    }

和上面三種獲取值的方法不同,* get * 方法只能返回 Optional 封裝的值豌熄,如果Optional 為空授嘀,方法會(huì)拋出 NoSuchElementException 異常。

    @Test(expected = NoSuchElementException.class)
    public void test_throwsNoSuchElementException() {
        String nullName = null;
        String name = Optional.ofNullable(nullName).get();
    }

拋出異常是 get API 的最大缺陷锣险,Optional 應(yīng)該幫助我們盡可能屏蔽這些不可見異常蹄皱,因此 get API 和 * Optional* 目標(biāo)相背而馳览闰,該方法將來可能被廢棄。應(yīng)該盡可能的使用其他方法獲取值巷折。

10. 使用 filter() 進(jìn)行過濾

filter API 被用于對(duì) Optional 封裝的值進(jìn)行一個(gè)內(nèi)聯(lián)測(cè)試焕济,filter API 使用一個(gè)謂詞作為參數(shù)并返回一個(gè)Optional 對(duì)象。如果盔几,被封裝的值通過測(cè)試則返回Optional 本身,否則返回一個(gè)空的 Optional 對(duì)象掩幢。

    @Test
    public void test_filter() throws Exception {
        Optional<Integer> passTest = Optional.of(101);
        assertTrue(passTest.filter(integer -> integer.intValue() > 100).isPresent());
        Optional<Integer> notPassTest = Optional.of(99);
        assertFalse(notPassTest.filter(integer -> integer.intValue() > 100).isPresent());
    }

filter API 的工作套路:根據(jù)某個(gè)預(yù)定義的規(guī)則拒絕 Optional 對(duì)象封裝的值逊拍,可以用于拒絕格式錯(cuò)誤的郵箱地址或強(qiáng)度不夠的密碼。

接下來看一個(gè)更有趣的例子(有些場(chǎng)景下不使用 Optional 為了安全的操作际邻,我們通常需要進(jìn)行多次 null 檢查)芯丧。假設(shè),我們打算購(gòu)買一部手機(jī)并且只關(guān)心手機(jī)的價(jià)格世曾。我們從手機(jī)購(gòu)買網(wǎng)站得到手機(jī)價(jià)格的推送消息缨恒,手機(jī)價(jià)格被封裝在一個(gè)對(duì)象中,數(shù)據(jù)結(jié)構(gòu)定義如下:

public class Phone {
    private Double price;

    public Phone(Double price) {
        this.price = price;
    }

    //standard getters and setters
}

當(dāng)把網(wǎng)址的推送數(shù)據(jù)傳遞給檢查手機(jī)價(jià)格是否滿足我們的預(yù)算要求的函數(shù)時(shí)(假設(shè)能接受的手機(jī)價(jià)格為3000-5000)轮听,如果不使用 * Optional* 一種可能的代碼實(shí)現(xiàn)如下:

    public boolean checkPriceWithoutOptional(Phone phone) {
        boolean isInRange = false;

        if (phone != null && phone.getPrice() != null
                && (phone.getPrice() >= 3000
                && phone.getPrice() <= 5000)) {

            isInRange = true;
        }
        return isInRange;
    }

為了實(shí)現(xiàn)上面的功能我們寫了很多代碼骗露,尤其在 if 的條件表達(dá)式中,函數(shù)真正的核心代碼僅僅是檢查價(jià)格范圍血巍,其他多余的檢查對(duì)于實(shí)現(xiàn)功能來說都是不必要的萧锉。代碼冗余可能并不是最嚴(yán)重的問題,忘記 null 檢查可能更加糟糕述寡,而這不會(huì)引發(fā)任何編譯錯(cuò)誤(代碼靜態(tài)檢查工具可以發(fā)現(xiàn)并上報(bào)告警)柿隙。

使用 Optionalfilter API 可以以一種優(yōu)雅的方式實(shí)現(xiàn)同樣的功能:

    public boolean checkPriceWithOptional(Phone phone) {
        return Optional.ofNullable(phone)
                .map(Phone::getPrice)
                .filter(p -> p >= 3000)
                .filter(p -> p <= 5000)
                .isPresent();
    }

使用 Optional 讓代碼在以下兩點(diǎn)優(yōu)于使用 if 語(yǔ)句檢查:

  • 給函數(shù)出入一個(gè) null 對(duì)象,不會(huì)觸發(fā)任何錯(cuò)誤鲫凶。
  • 代碼更加聚焦業(yè)務(wù)實(shí)現(xiàn)(價(jià)格檢查)禀崖,其他的事情由 Optional 負(fù)責(zé)。

11. 使用 map() 進(jìn)行值變換

在之前的章節(jié)螟炫,我們已經(jīng)看到如何使用過濾器接受或拒絕 Optional 封裝的值波附。相同的語(yǔ)法可以用于 map API 對(duì) Optional 封裝的值進(jìn)行變換。

    @Test
    public void test_mapList2ListSize() {
        List<String> companyNames = Arrays.asList(
                "Java", "C++", "", "C", "", "Python");
        Optional<List<String>> listOptional = Optional.of(companyNames);

        int size = listOptional
                .map(List::size)
                .orElse(0);
        assertEquals(6, size);
    }

在上面的例子中不恭,我們使用 Optional 封裝了一個(gè)字符串列表叶雹,并使用 map API 對(duì) 字符串列表進(jìn)行變換,上面例子中執(zhí)行的變化是獲取字符串列表的長(zhǎng)度换吧。

map API 返回對(duì) Optional 封裝對(duì)象的計(jì)算結(jié)果折晦,最后需要調(diào)用合適的API來獲取Optional 對(duì)象的值(變換后的值)。

注意:filter API 值檢查 Optional 對(duì)象封裝的值并返回一個(gè)boolean類型的結(jié)果,相反 map API 對(duì) Optional 對(duì)象封裝的值進(jìn)行計(jì)算并返回計(jì)算結(jié)果沾瓦。

    @Test
    public void test_mapString2StringSize() {
        String name = "Hello World";
        Optional<String> nameOptional = Optional.of(name);

        int len = nameOptional
                .map(String::length)
                .orElse(0);
        assertEquals(11, len);
    }

我們可以鏈?zhǔn)秸{(diào)用 mapfilter API 來做一些更有意義的事情满着。假設(shè)谦炒,我們有一段代碼需要檢查用戶輸入的密碼是否正確,我們可以使用 map 對(duì)密碼進(jìn)行變換风喇,使用 filter 判斷密碼是否正確:

    @Test
    public void test_checkPassword() {
        String password = " password ";
        Optional<String> passOpt = Optional.of(password);
        boolean correctPassword = passOpt.filter(
            pass -> pass.equals("password")).isPresent();
        assertFalse(correctPassword);

        correctPassword = passOpt
            .map(String::trim)
            .filter(pass -> pass.equals("password"))
            .isPresent();
        assertTrue(correctPassword);
    }
}

12. 使用 flatMap() 對(duì)值進(jìn)行變換

map API 一樣宁改,我們也可以使用 flatMap API 作為一個(gè)替代方法對(duì)值進(jìn)行變換。兩者的主要區(qū)別是:map 值對(duì)未封裝的值進(jìn)行轉(zhuǎn)換魂莫,flatMap 在處理值之前先進(jìn)行“去封裝”操作还蹲,然后再執(zhí)行變換操作。
為了更清晰的解釋兩者的區(qū)別耙考,我們假設(shè)有一個(gè)Person對(duì)象谜喊,對(duì)象有三個(gè)基本屬性:名字、年齡和密碼倦始。

public class Person {
    private String name;
    private int age;
    private String password;

    public Person() {
    }

    public Person(String name, int age, String password) {
        this.name = name;
        this.age = age;
        this.password = password;
    }

    public Optional<String> getName() {
        return Optional.ofNullable(name);
    }

    public Optional<Integer> getAge() {
        return Optional.ofNullable(age);
    }

    public Optional<String> getPassword() {
        return Optional.ofNullable(password);
    }

    // normal constructors and setters
}

我們創(chuàng)建一個(gè)Person對(duì)象斗遏,并使用 Optional 封裝創(chuàng)建的Person對(duì)象:

        Person person = new Person("john", 26, "pwd");
        Optional<Person> personOptional = Optional.of(person);

分別使用 mapflatMap API 獲取名字的代碼如下,從中可以看到使用 flatMap API 的代碼量較使用 map 更短小鞋邑,也更加容易理解诵次。

    @Test
    public void test_flatMap() {
        Person person = new Person("ct", 26,"pwd");
        Optional<Person> personOptional = Optional.of(person);

        Optional<Optional<String>> nameOptionalWrapper
            = personOptional.map(Person::getName);
        Optional<String> nameOptional
            = nameOptionalWrapper.orElseThrow(IllegalArgumentException::new);
        String name1 = nameOptional.orElse("");
        assertEquals("ct", name1);

        String name = personOptional
            .flatMap(Person::getName)
            .orElse("");
        assertEquals("ct", name);
    }

13. 總結(jié)

本文簡(jiǎn)要介紹了Java 8 Optional 類的大部分重要特性,與此同時(shí)枚碗,我們也簡(jiǎn)單闡述了為什么我們選擇使用Optional 代替顯示的 null 檢查和參數(shù)檢查逾一。最后,講解了 orElseorElseGet 之間微妙但重要的區(qū)別视译,關(guān)于該主題可以從拓展閱讀獲取更多內(nèi)容嬉荆。

文中的樣例代碼可以從 GitHub.獲取。

參考

[1] Guide To Java 8 Optional
[2] Java 8 Optional
[3] Java 11 Optional

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末酷含,一起剝皮案震驚了整個(gè)濱河市鄙早,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌椅亚,老刑警劉巖限番,帶你破解...
    沈念sama閱讀 216,919評(píng)論 6 502
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異呀舔,居然都是意外死亡弥虐,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,567評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門媚赖,熙熙樓的掌柜王于貴愁眉苦臉地迎上來霜瘪,“玉大人,你說我怎么就攤上這事惧磺∮倍裕” “怎么了?”我有些...
    開封第一講書人閱讀 163,316評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵磨隘,是天一觀的道長(zhǎng)缤底。 經(jīng)常有香客問我顾患,道長(zhǎng),這世上最難降的妖魔是什么个唧? 我笑而不...
    開封第一講書人閱讀 58,294評(píng)論 1 292
  • 正文 為了忘掉前任江解,我火速辦了婚禮,結(jié)果婚禮上徙歼,老公的妹妹穿的比我還像新娘犁河。我一直安慰自己,他們只是感情好魄梯,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,318評(píng)論 6 390
  • 文/花漫 我一把揭開白布呼股。 她就那樣靜靜地躺著,像睡著了一般画恰。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上吸奴,一...
    開封第一講書人閱讀 51,245評(píng)論 1 299
  • 那天允扇,我揣著相機(jī)與錄音,去河邊找鬼则奥。 笑死考润,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的读处。 我是一名探鬼主播糊治,決...
    沈念sama閱讀 40,120評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼罚舱!你這毒婦竟也來了井辜?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 38,964評(píng)論 0 275
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤管闷,失蹤者是張志新(化名)和其女友劉穎粥脚,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體包个,經(jīng)...
    沈念sama閱讀 45,376評(píng)論 1 313
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡刷允,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,592評(píng)論 2 333
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了碧囊。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片树灶。...
    茶點(diǎn)故事閱讀 39,764評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖糯而,靈堂內(nèi)的尸體忽然破棺而出天通,到底是詐尸還是另有隱情,我是刑警寧澤歧蒋,帶...
    沈念sama閱讀 35,460評(píng)論 5 344
  • 正文 年R本政府宣布土砂,位于F島的核電站州既,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏萝映。R本人自食惡果不足惜吴叶,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,070評(píng)論 3 327
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望序臂。 院中可真熱鬧蚌卤,春花似錦、人聲如沸奥秆。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,697評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)构订。三九已至侮叮,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間悼瘾,已是汗流浹背囊榜。 一陣腳步聲響...
    開封第一講書人閱讀 32,846評(píng)論 1 269
  • 我被黑心中介騙來泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留亥宿,地道東北人卸勺。 一個(gè)月前我還...
    沈念sama閱讀 47,819評(píng)論 2 370
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像烫扼,于是被迫代替她去往敵國(guó)和親曙求。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,665評(píng)論 2 354

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

  • Optional 本章內(nèi)容 如何為缺失的值建模 Optional 類 應(yīng)用Optional的幾種模式 使用Opti...
    追憶逝水年華閱讀 1,792評(píng)論 0 0
  • 本文獲得Stackify授權(quán)翻譯發(fā)表,轉(zhuǎn)載需要注明來自公眾號(hào)EAWorld堰氓。 作者:EUGEN PARASCHIV...
    72a1f772fe47閱讀 11,704評(píng)論 3 7
  • 這篇文章寫得不錯(cuò)芽淡,所以轉(zhuǎn)載了下,修改了小部分豆赏,原文地址見末尾 身為一名Java程序員挣菲,大家可能都有這樣的經(jīng)歷:調(diào)用...
    瘋狂的冰塊閱讀 275評(píng)論 0 2
  • 文/黃煊墨 我小時(shí)候睡懶覺白胀、干活偷懶,我媽必“詛咒”我抚岗,長(zhǎng)大娶不到媳婦或杠。當(dāng)時(shí)太小,不知道媳婦是什么東西宣蔚,但老太太總...
    煊墨雜談閱讀 646評(píng)論 0 0
  • 人生的路有平坦也有坎坷 平坦坎坷都得走 人生的河有深也有淺 深淺都得趟 人生的風(fēng)景有美麗也有蕭條 美麗蕭條都得欣賞...
    文采樂閱讀 329評(píng)論 8 14