java8 -Optional

厭倦了空指針異常? 考慮使用Java SE 8的Optional分扎!使代碼更具可讀性并使得免受空指針異常的影響馍资。
有人曾經(jīng)說(shuō)過(guò)悲没,在未處理空指針異常之前,你不是真正的Java程序員聊替。 開(kāi)玩笑說(shuō),空引用是許多問(wèn)題的根源,因?yàn)樗ǔ1硎救鄙僦怠?Java SE 8引入了一個(gè)名為java.util.Optional的新類眶拉,可以緩解一些這樣的問(wèn)題。
讓我們從一個(gè)例子開(kāi)始憔儿,看看空指針的危險(xiǎn)性忆植。 下面是一個(gè)計(jì)算機(jī)的嵌套對(duì)象結(jié)構(gòu),如圖所示:

嵌套對(duì)象結(jié)構(gòu)

下面的代碼可能會(huì)產(chǎn)生什么問(wèn)題谒臼?

String version = computer.getSoundcard().getUSB().getVersion();

這段代碼看起來(lái)沒(méi)什么問(wèn)題呀朝刊。但是,好多計(jì)算機(jī)(比如Raspberry Pi)實(shí)際上并沒(méi)有安裝聲卡(sound card)蜈缤,那么getSoundcard()得到得結(jié)果是什么呢拾氓?
那么一般來(lái)說(shuō)是返回空引用表示沒(méi)有聲卡。 不幸的是劫樟,getUSB()將嘗試返回空引用的USB端口痪枫,這將在運(yùn)行時(shí)導(dǎo)致NullPointerException织堂,程序奔潰。 想象一下奶陈,如果你的程序在生產(chǎn)環(huán)境上運(yùn)行; 如果程序突然報(bào)錯(cuò)易阳,你的客戶會(huì)有什么反應(yīng)?
空指針是有一些歷史背景吃粒,計(jì)算機(jī)科學(xué)巨頭Tony Hoare寫道: "I call it my billion-dollar mistake. It was the invention of the null reference in 1965. I couldn't resist the temptation to put in a null reference, simply because it was so easy to implement."
怎么做才能防止空指針異常發(fā)生呢潦俺?你可以通過(guò)判斷是否為null來(lái)防止NullPointerException,如下所示:

例1
String version = "UNKNOWN";
if(computer != null){
  Soundcard soundcard = computer.getSoundcard();
  if(soundcard != null){
    USB usb = soundcard.getUSB();
    if(usb != null){
      version = usb.getVersion();
    }
  }
}

但是徐勃,通過(guò)例1我們看到事示,代碼由于嵌套檢查變得非常難看。不幸的是僻肖,我們需要很多這樣得代碼來(lái)確保不會(huì)發(fā)生NullPointerException肖爵。 此外,業(yè)務(wù)邏輯中若含有這些檢查臀脏,會(huì)讓我們很是厭煩劝堪。 事實(shí)上,這些代碼也降低我們系統(tǒng)的整體可讀性揉稚。
此外秒啦,嵌套檢查也是一個(gè)容易出錯(cuò)的過(guò)程; 如果你忘記檢查一個(gè)屬性是否為空怎么辦? 本文將論證使用null表示空值是一種錯(cuò)誤的方法搀玖。 我們需要一種更好的方法來(lái)表達(dá)空值和非空值余境。
為了給出一些上下文,讓我們簡(jiǎn)要介紹一下其他編程語(yǔ)言提供的解決方式灌诅。

其他語(yǔ)言的替代方案是什么芳来?

例如Groovy、C#等語(yǔ)言有一個(gè)由"?."表示的安全導(dǎo)航操作符猜拾,用來(lái)保護(hù)出現(xiàn)在屬性路徑中 null 值绣张。如下所示:

String version = computer?.getSoundcard()?.getUSB()?.getVersion();

在這種情況下,如果computer為null关带,則變量version 將被賦值為null,或者getSoundcard() 返回null沼撕,或者getUSB()返回null宋雏。 你不需要編寫復(fù)雜的嵌套條件來(lái)檢查是否為null。
此外务豺,Groovy還包括Elvis運(yùn)算符"?:"(至于為什么叫Elvis運(yùn)算符磨总,是因?yàn)??:'跟一個(gè)叫Elvis的搖滾明星(貓王)的發(fā)型很像。)笼沥,當(dāng)需要默認(rèn)值時(shí)蚪燕,使用Elvis運(yùn)算符會(huì)使表達(dá)式更簡(jiǎn)潔娶牌。 下面,如果使用安全導(dǎo)航操作符的表達(dá)式返回null馆纳,則返回默認(rèn)值"UNKNOWN"; 否則诗良,返回可用的version。如下所示:

String version = 
    computer?.getSoundcard()?.getUSB()?.getVersion() ?: "UNKNOWN";

其他函數(shù)語(yǔ)言(如Haskell和Scala)采用不同的解決方案鲁驶。 Haskell包含一個(gè)Maybe類型鉴裹,它基本上封裝了一個(gè)可選值。 Maybe類型的值可以包含給定類型的值钥弯,也可以不包含任何值径荔,沒(méi)有空引用的概念。 Scala有一個(gè)名為Option[T]的類似構(gòu)造來(lái)封裝類型T值的存在或缺失脆霎。然后总处,你必須使用Option顯式檢查是否存在值,這強(qiáng)制了"null checking.". 你再也不能“忘記這樣做”睛蛛,因?yàn)樗怯深愋拖到y(tǒng)強(qiáng)制執(zhí)行的鹦马。
好吧,我們似乎偏離了主題玖院,而且這些聽(tīng)起來(lái)都相當(dāng)抽象菠红。 你現(xiàn)在可能會(huì)想,“那么难菌,Java SE 8呢试溯?”

Optional 介紹

Java SE 8引入了一個(gè)名為 java.util.Optional<T>的新類,受Haskell和Scala思想的啟發(fā)郊酒。 它是一個(gè)封裝可選值的類遇绞,你可以將Optional視為一個(gè)單值容器,可以包含值燎窘,也不包含值的(然后將其視為“空” )摹闽。如圖所示:

An optional sound card

接下來(lái),我們可以使用Optional更改一下例1的代碼:

例2
public class Computer {
  private Optional<Soundcard> soundcard;  
  public Optional<Soundcard> getSoundcard() { ... }
  ...
}

public class Soundcard {
  private Optional<USB> usb;
  public Optional<USB> getUSB() { ... }

}

public class USB{
  public String getVersion(){ ... }
}

例2中的代碼立即顯示計(jì)算機(jī)可能有聲卡,也可能沒(méi)有聲卡(聲卡是可選的)褐健。 此外付鹿,聲卡可以選配USB端口。 這是一種改進(jìn)蚜迅,因?yàn)檫@個(gè)新模型現(xiàn)在可以清楚地反映出是否允許丟失給定值舵匾。 請(qǐng)注意,類似的想法已在諸如Guava等類庫(kù)中早已提供谁不。
但是我們以用Optional<Soundcard>對(duì)象實(shí)際做些什么呢坐梯? 最終只是想要獲得USB端口的版本號(hào)。 簡(jiǎn)而言之刹帕,Optional類包括處理存在或不存在值的情況的方法吵血。 但是谎替,與空引用(null)相比的優(yōu)點(diǎn)是:Optional類強(qiáng)制你在值不存在時(shí)考慮該情況。 因此蹋辅,你能更有效地防止代碼中出現(xiàn)不期而至的空指針異常钱贯。
值得注意的是,Optional類的意圖不是替換空引用晕翠。 相反喷舀,它的目的是幫助設(shè)計(jì)更易于理解的API,這樣只需讀取方法的簽名淋肾,就能了解該方法是否接受一個(gè)Optional類型的值硫麻。 這會(huì)強(qiáng)制你主動(dòng)解包Optional以處理空值。

采用Optional模式

廢話我們就不多說(shuō)了; 讓我們看看代碼吧樊卓! 首先我們將探討如何使用Optional重寫典型的空檢查模式拿愧。 在本文結(jié)束時(shí),你將了解如何使用Optional來(lái)重寫例1中執(zhí)行多個(gè)嵌套空檢查碌尔,如下所示:

String name = computer.flatMap(Computer::getSoundcard)
                          .flatMap(Soundcard::getUSB)
                          .map(USB::getVersion)
                          .orElse("UNKNOWN");

Note: 確保了解Java SE 8 lambdas和方法引用語(yǔ)法(請(qǐng)參閱Java 8:Lambdas)及其流管道概念(請(qǐng)參閱使用Java SE 8 Streams處理數(shù)據(jù))浇辜。

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

首先,如何創(chuàng)建Optional對(duì)象呢唾戚? 有如下幾種方法:

  1. 聲明一個(gè)空的Optional
Optional<Soundcard> sc = Optional.empty();
  1. 依據(jù)一個(gè)非空值創(chuàng)建Optional
SoundCard soundcard = new Soundcard();
Optional<Soundcard> sc = Optional.of(soundcard); 

如果soundcard是一個(gè)null柳洋,這段代碼會(huì)立即拋出一個(gè)NullPointerException,而不是等到你試圖訪問(wèn)soundcard 的屬性值時(shí)才返回一個(gè)錯(cuò)誤叹坦。

  1. 可接受null的Optional
    最后熊镣,使用靜態(tài)工廠方法Optional.ofNullable,你可以創(chuàng)建一個(gè)允許null值的Optional 對(duì)象:
Optional<Soundcard> sc = Optional.ofNullable(soundcard); 

如果Soundcard是null募书,那么得到的Optional對(duì)象就是個(gè)空對(duì)象绪囱。

如果值存在,那么就做點(diǎn)什么吧

現(xiàn)在你有了一個(gè)Optional對(duì)象莹捡,你可以使用可用的方法來(lái)顯式處理其值鬼吵,無(wú)論值存在與否。 而不是必須進(jìn)行空檢查篮赢,如下所示:

SoundCard soundcard = ...;
if(soundcard != null){
  System.out.println(soundcard);
}

上訴代碼你可以使用* ifPresent()*進(jìn)行重寫齿椅,如下所示:

Optional<Soundcard> soundcard = ...;
soundcard.ifPresent(System.out::println);

你無(wú)需再進(jìn)行顯式空檢查; 它由類型系統(tǒng)強(qiáng)制執(zhí)行。 如果Optional對(duì)象為空启泣,則不會(huì)打印任何內(nèi)容媒咳。
你可以使用isPresent()方法來(lái)判斷Optional對(duì)象中是否存在值。此外种远,還由一個(gè)get()方法,顽耳。如果變量存在坠敷,它直接返回封裝的變量 值妙同,否則就拋出一個(gè)NoSuchElementException異常。這兩個(gè)方法組合使用膝迎,可以防止異常的發(fā)生粥帚。如下所示:

if(soundcard.isPresent()){
  System.out.println(soundcard.get());
}

但是,這不是Optional的推薦用法(它對(duì)嵌套空值檢查沒(méi)有太大改進(jìn))限次,還有更好的替代方案芒涡,我們將在下面討論。

默認(rèn)行為及解引用 Optional 對(duì)象

如果返回值為null卖漫,通常的處理方式是給定一個(gè)默認(rèn)值费尽。 一般來(lái)說(shuō),你可以使用三元運(yùn)算符來(lái)實(shí)現(xiàn)此目的羊始,如下所示:

Soundcard soundcard = 
  maybeSoundcard != null ? maybeSoundcard 
            : new Soundcard("basic_sound_card");

使用Optional對(duì)象, 你可以使用orElse()方法重寫上面的代碼 旱幼,使用這種方式你還可以定義一個(gè)默認(rèn)值,遭遇空的Optional變量時(shí)突委,默認(rèn)值會(huì)作為該方法的調(diào)用返回值柏卤。如下所示:

Soundcard soundcard = maybeSoundcard.orElse(new Soundcard("defaut"));

同理,你也可以使用orElseThrow()方法匀油,與orElse()方法不同的是缘缚,使用orElseThrow()時(shí),當(dāng)遭遇Optional對(duì)象為空時(shí)都會(huì)拋出一個(gè)異常敌蚜,但是使用orElseThrow你可以定制希 望拋出的異常類型桥滨。 如下所示:

Soundcard soundcard = 
  maybeSoundCard.orElseThrow(IllegalStateException::new);

使用filter 剔除特定的值

你經(jīng)常需要調(diào)用某個(gè)對(duì)象的方法,查看它的某些屬性钝侠。比如该园,你可能需要檢查USB端口是否為3.0版本。為了以一種安全的方式進(jìn)行這些操作帅韧,你首先需要確定引用指向的USB 對(duì)象是否為null里初,之后再調(diào)用它的getVersion()方法,如下所示:

USB usb = ...;
if(usb != null && "3.0".equals(usb.getVersion())){
  System.out.println("ok");
}

使用Optional對(duì)象的filter方法忽舟,這段代碼可以重構(gòu)如下:

Optional<Insurance> optInsurance = ...; optInsurance.filter(insurance ->                         "CambridgeInsurance".equals(insurance.getName()))             .ifPresent(x -> System.out.println("ok"));

filter方法接受一個(gè)謂詞作為參數(shù)双妨。如果Optional對(duì)象的值存在,并且它符合謂詞的條件叮阅, filter方法就返回其值刁品;否則它就返回一個(gè)空的Optional對(duì)象。你可以將 Optional看成包含一個(gè)元素的Stream對(duì)象浩姥,這個(gè)方法的行為就非常清晰了挑随。如果Optional 對(duì)象為空,它不做任何操作勒叠,反之兜挨,它就對(duì)Optional對(duì)象中包含的值施加謂詞操作膏孟。如果該操 作的結(jié)果為true,它不做任何改變拌汇,直接返回該Optional對(duì)象柒桑,否則就將該值過(guò)濾掉,將 Optional的值置空噪舀。

使用map 從 Optional 對(duì)象中提取和轉(zhuǎn)換值

從對(duì)象中提取信息是一種比較常見(jiàn)的模式魁淳。比如,你可能想要從Soundcard 對(duì)象中提取USB對(duì)象与倡。提取之前界逛,你需要檢查Soundcard對(duì)象是否為null,然后進(jìn)一步檢查它的version是否正確蒸走,你可能會(huì)寫如下代碼:

if(soundcard != null){
  USB usb = soundcard.getUSB();
  if(usb != null && "3.0".equals(usb.getVersion()){
    System.out.println("ok");
  }
}

我們可以使用map方法重寫這種 "checking for null and extracting" (這里是Soundcard對(duì)象)的模式仇奶。

Optional<Soundcard> maybeSoundcard= Optional.ofNullable(soundcard); Optional<USB> usb = maybeSoundcard.map(Soundcard::getUSB);

從概念上,這與Stream的map方法相差無(wú)幾比驻。map操作會(huì)將提供的函數(shù)應(yīng)用于流的每個(gè)元素该溯。如 果Stream為空,就什么也不做别惦。
Optional類的map方法完全相同:你可以把Optional對(duì)象看成一種特殊的集合數(shù)據(jù)狈茉,它至多包含一個(gè)元素。如果Optional包含一個(gè)值掸掸,那函數(shù)(這里是提取USB端口的方法引用)就將該值作為參數(shù)傳遞給map氯庆,對(duì)該值進(jìn)行轉(zhuǎn)換。如果Optional為空扰付,就什么也不做堤撵。
最后,我們可以結(jié)合map方法和filter方法重寫上面的代碼羽莺,剔除版本不同于3.0的USB端口:

maybeSoundcard.map(Soundcard::getUSB)
      .filter(usb -> "3.0".equals(usb.getVersion())
      .ifPresent(() -> System.out.println("ok"));

真棒; 我們的代碼開(kāi)始更接近問(wèn)題陳述实昨,并且沒(méi)有重復(fù)的嵌套空檢查妨礙我們!

使用flatMap 鏈接 Optional 對(duì)象

我們已經(jīng)使用Optional重構(gòu)了一些以前的代碼盐固,那么我們?nèi)绾我园踩姆绞骄帉懸韵麓a呢荒给?

String version = computer.getSoundcard().getUSB().getVersion();

請(qǐng)注意,這些代碼的意思都是從另一個(gè)對(duì)象中提取一個(gè)對(duì)象刁卜,這正是map方法的用處志电。 在本文前面,我們更改了model蛔趴,因此Computer具有Optional<Soundcard>挑辆,Soundcard 具有 Optional<USB>,因此我們可以利用map重寫之前的代碼:

String version = computer.map(Computer::getSoundcard)
                  .map(Soundcard::getUSB)
                  .map(USB::getVersion)
                  .orElse("UNKNOWN");

不幸的是,這段代碼無(wú)法通過(guò)編譯之拨。為什么呢茉继?computer是 Optional<Computer>類型的變量, 調(diào)用map方法應(yīng)該沒(méi)有問(wèn)題蚀乔。但getSoundcard() 返回的是一個(gè)Optional<Soundcard>類型的對(duì)象,這意味著map操作的結(jié)果是一個(gè)Optional<Optional<Soundcard>>類型的對(duì)象菲茬。因 此吉挣,它對(duì)getUSB()的調(diào)用是非法的,因?yàn)樽钔鈱拥膐ptional對(duì)象包含了另一個(gè)optional 對(duì)象的值婉弹,而它當(dāng)然不會(huì)支持e getUSB()方法睬魂。下圖說(shuō)明了你會(huì)遭遇的嵌套式optional 結(jié)構(gòu)。


A two-level Optional

所以镀赌,我們?cè)撊绾谓鉀Q這個(gè)問(wèn)題呢氯哮?讓我們?cè)倩仡櫼幌略诹魃鲜褂眠^(guò)的模式: flatMap方法。使用流時(shí)商佛,flatMap方法接受一個(gè)函數(shù)作為參數(shù)喉钢,這個(gè)函數(shù)的返回值是另一個(gè)流。 這個(gè)方法會(huì)應(yīng)用到流中的每一個(gè)元素良姆,終形成一個(gè)新的流的流肠虽。但是flagMap會(huì)用流的內(nèi)容替 換每個(gè)新生成的流。換句話說(shuō)玛追,由方法生成的各個(gè)流會(huì)被合并或者扁平化為一個(gè)單一的流税课。這里你希望的結(jié)果其實(shí)也是類似的,但是你想要的是將兩層的optional合并為一個(gè)痊剖。
好吧韩玩,這是個(gè)好消息:Optional也支持flatMap方法。 它的目的是將轉(zhuǎn)換函數(shù)應(yīng)用于Optional的值(就像map操作一樣)陆馁,然后將兩層的optional合并為一個(gè)找颓。 下圖說(shuō)明了transform函數(shù)返回Optional對(duì)象時(shí)mapflatMap之間的區(qū)別。

Using map versus flatMap with Optional

因此氮惯,相信現(xiàn)在你已經(jīng)對(duì)Optional的map和flatMap方法有了一定的了解叮雳,讓我們看看如何應(yīng)用。我們需要使用flatMap重寫上面的代碼妇汗,如下所示:

String version = computer.flatMap(Computer::getSoundcard)
                   .flatMap(Soundcard::getUSB)
                   .map(USB::getVersion)
                   .orElse("UNKNOWN");

第一個(gè)flatMap確保返回Optional<Soundcard> 對(duì)象帘不,而不是Optional<Optional<Soundcard>>,同理杨箭,第二個(gè)flatMap返回 Optional<USB>對(duì)象寞焙。 請(qǐng)注意,第三個(gè)調(diào)用需要map()方法,因?yàn)間etVersion()返回String對(duì)象捣郊,而不是Optional對(duì)象辽狈。
哇! 從編寫痛苦的嵌套空檢查到編寫組合代碼呛牲,再到可讀性強(qiáng)刮萌,更有效地防止代碼中出現(xiàn)不期而至的空指針異常,我們已經(jīng)做的越來(lái)越好了娘扩。

結(jié)論

在本文中着茸,我們已經(jīng)學(xué)習(xí)了如何采用新的Java SE 8 java.util.Optional<T>。 Optional的目的不是替換代碼中的每個(gè)空引用琐旁,而是幫助設(shè)計(jì)更好的API涮阔,只需讀取方法的簽名 - 就能了解該方法是否接受一個(gè)Optional類型的值。 此外灰殴,Optional強(qiáng)制主動(dòng)解包Optional以處理空值; 因此敬特,可以保護(hù)代碼免受意外的空指針異常的影響。
相關(guān)代碼請(qǐng)參見(jiàn)我的github optionalExample

附錄:

\color{#654321}{\Large\mathbf{Optional類的方法 }}

方 法 描 述
empty 返回一個(gè)空的 Optional 實(shí)例
filter 如果值存在并且滿足提供的謂詞牺陶,就返回包含該值的 Optional 對(duì)象伟阔;否則返回一個(gè)空的 Optional 對(duì)象
flatMap 如果值存在,就對(duì)該值執(zhí)行提供的 mapping函數(shù)調(diào)用义图,返回一個(gè) Optional 類型的值减俏,否則就返 回一個(gè)空的 Optional 對(duì)象
get 如果該值存在,將該值用 Optional 封裝返回碱工,否則拋出一個(gè) NoSuchElementException 異常
ifPresent 如果值存在娃承,就執(zhí)行使用該值的方法調(diào)用,否則什么也不做
isPresent 如果值存在就返回 true怕篷,否則返回 false
map 如果值存在历筝,就對(duì)該值執(zhí)行提供的 mapping函數(shù)調(diào)用
of 將指定值用 Optional 封裝之后返回,如果該值為 null廊谓,則拋出一個(gè) NullPointerException 異常
ofNullable 將指定值用 Optional 封裝之后返回梳猪,如果該值為 null,則返回一個(gè)空的 Optional 對(duì)象
orElse 如果有值則將其返回蒸痹,否則返回一個(gè)默認(rèn)值
orElseGet 如果有值則將其返回春弥,否則返回一個(gè)由指定的 Supplier 接口生成的值
orElseThrow 如果有值則將其返回,否則拋出一個(gè)由指定的 Supplier 接口生成的異常
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末叠荠,一起剝皮案震驚了整個(gè)濱河市匿沛,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌榛鼎,老刑警劉巖逃呼,帶你破解...
    沈念sama閱讀 212,542評(píng)論 6 493
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件鳖孤,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡抡笼,警方通過(guò)查閱死者的電腦和手機(jī)苏揣,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,596評(píng)論 3 385
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)推姻,“玉大人平匈,你說(shuō)我怎么就攤上這事〔毓牛” “怎么了吐葱?”我有些...
    開(kāi)封第一講書人閱讀 158,021評(píng)論 0 348
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)校翔。 經(jīng)常有香客問(wèn)我,道長(zhǎng)灾前,這世上最難降的妖魔是什么防症? 我笑而不...
    開(kāi)封第一講書人閱讀 56,682評(píng)論 1 284
  • 正文 為了忘掉前任,我火速辦了婚禮哎甲,結(jié)果婚禮上蔫敲,老公的妹妹穿的比我還像新娘。我一直安慰自己炭玫,他們只是感情好奈嘿,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,792評(píng)論 6 386
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著吞加,像睡著了一般裙犹。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上衔憨,一...
    開(kāi)封第一講書人閱讀 49,985評(píng)論 1 291
  • 那天叶圃,我揣著相機(jī)與錄音,去河邊找鬼践图。 笑死掺冠,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的码党。 我是一名探鬼主播德崭,決...
    沈念sama閱讀 39,107評(píng)論 3 410
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼揖盘!你這毒婦竟也來(lái)了眉厨?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書人閱讀 37,845評(píng)論 0 268
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤扣讼,失蹤者是張志新(化名)和其女友劉穎缺猛,沒(méi)想到半個(gè)月后缨叫,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,299評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡荔燎,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,612評(píng)論 2 327
  • 正文 我和宋清朗相戀三年耻姥,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片有咨。...
    茶點(diǎn)故事閱讀 38,747評(píng)論 1 341
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡琐簇,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出座享,到底是詐尸還是另有隱情婉商,我是刑警寧澤,帶...
    沈念sama閱讀 34,441評(píng)論 4 333
  • 正文 年R本政府宣布渣叛,位于F島的核電站丈秩,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏淳衙。R本人自食惡果不足惜蘑秽,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 40,072評(píng)論 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望箫攀。 院中可真熱鬧肠牲,春花似錦、人聲如沸靴跛。這莊子的主人今日做“春日...
    開(kāi)封第一講書人閱讀 30,828評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)梢睛。三九已至肥印,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間扬绪,已是汗流浹背竖独。 一陣腳步聲響...
    開(kāi)封第一講書人閱讀 32,069評(píng)論 1 267
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留挤牛,地道東北人莹痢。 一個(gè)月前我還...
    沈念sama閱讀 46,545評(píng)論 2 362
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像墓赴,于是被迫代替她去往敵國(guó)和親竞膳。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,658評(píng)論 2 350