一文讀懂 kotlin 的協(xié)變與逆變 -- 從 Java 說(shuō)起

前言

為了更好地理解 kotlin 和 Java 中的協(xié)變與逆變恕出,先看一些基礎(chǔ)知識(shí)。

普通賦值

在 Java 中嘁酿,常見(jiàn)的賦值語(yǔ)句如下:

A a = b;

賦值語(yǔ)句必須滿足的條件是:左邊要么是右邊的父類(lèi)徙硅,要么和右邊類(lèi)型一樣即碗。即 A 的類(lèi)型要“大于”B 的類(lèi)型焰情,比如 Object o = new String("s"); 。為了方便起見(jiàn)剥懒,下文中稱(chēng)作 A > B内舟。

除了上述最常見(jiàn)的賦值語(yǔ)句,還有兩種其他的賦值語(yǔ)句:

函數(shù)參數(shù)的賦值

public void fun(A a) {}
// 調(diào)用處賦值
B b = new B();
fun(b);

在調(diào)用 fun(b) 方法時(shí)初橘,會(huì)將傳入的 B b 實(shí)參賦值給形參 A a谒获,即 A a = b 的形式。同樣的壁却,必須要滿足形參類(lèi)型大于實(shí)參批狱,即 A > B。

函數(shù)返回值的賦值

public A fun() {
    return (B)b;
} 

函數(shù)返回值類(lèi)型接收實(shí)際返回類(lèi)型的值展东,實(shí)際返回類(lèi)型 B b 相當(dāng)于賦值給了函數(shù)返回值類(lèi)型 A a赔硫,即 B b 賦值給了 A a, 即 A a = b,那么必須滿足 A > B 的類(lèi)型關(guān)系盐肃。

所以爪膊,無(wú)論哪種賦值,都必須滿足左邊類(lèi)型 > 右邊類(lèi)型砸王,即 A > B推盛。

Java 中的協(xié)變與逆變

有了前面的基礎(chǔ)知識(shí),就可以方便地解釋協(xié)變與逆變了谦铃。

如果類(lèi) A > 類(lèi) B耘成,經(jīng)過(guò)一個(gè)變化 trans 后得到的 trans(A) 與 trans(B) 依舊滿足 trans(A) > trans(B),那么稱(chēng)為協(xié)變

逆變則剛好相反瘪菌,如果類(lèi) A > 類(lèi) B撒会,經(jīng)過(guò)一個(gè)變化 trans 后得到的 trans(A) 與 trans(B) 滿足 trans(B) > trans(A),稱(chēng)為逆變师妙。

比如大家都知道 Java 的數(shù)組是協(xié)變的诵肛,假如 A > B,那么有 A[] > B[]默穴,所以 B[] 可以賦值給 A[]怔檩。舉個(gè)例子:

Integer[] nums = new Integer[]{};
Object[] o = nums; // 可以賦值,因?yàn)閿?shù)組的協(xié)變特性所以 Object[] > int[]

但是 Java 的泛型則不滿足協(xié)變蓄诽,如下:

List<Integer> l = new ArrayList<>();
List<Object> o = l;// 這里會(huì)報(bào)錯(cuò)珠洗,不能編譯

上述代碼報(bào)錯(cuò),就是因?yàn)槿糇ǎm然 Object > Integer,但是由于泛型不滿足協(xié)變蝴猪,所以 List<Object> > List<Integer> 是不能滿足的调衰,既然不滿足左邊大于右邊這個(gè)條件,從前言中我們知道自阱,自然就不能將 List<Integer> 賦值給 List<Object>嚎莉。一般稱(chēng) Java 泛型不支持型變。

Java 中泛型如何實(shí)現(xiàn)協(xié)變與逆變

從前面我們知道沛豌,在 Java 中泛型是不支持型變的趋箩,但是這會(huì)產(chǎn)生一個(gè)讓人很奇怪的疑惑,也是很多講泛型的文章中提到的:

如果 B 是 A 的子類(lèi)加派,那么 List<B> 就應(yīng)該是 List<A> 的子類(lèi)呀叫确!這是一個(gè)非常自然而然的想法!

但是很抱歉芍锦,由于種種原因竹勉,Java 并不支持。但是娄琉,Java 并不是完全抹殺了泛型的型變特性次乓,Java 提供了 <? extends T> 和 <? super T> 使泛型擁有協(xié)變和逆變的特性。

<? extends T> 與 <? super T>

<? extends T> 稱(chēng)為上界通配符孽水,<? super T> 稱(chēng)為下界通配符票腰。使用上界通配符可以使泛型協(xié)變,而使用下界通配符可以使泛型逆變女气。

比如之前舉的例子

List<Integer> l = new ArrayList<>();
List<Object> o = l;// 這里會(huì)報(bào)錯(cuò)杏慰,不能編譯

如果使用上界通配符,

List<Integer> l = new ArrayList<>();
List<? extends Object> o = l;// 可以通過(guò)編譯

這樣,List<? extends Object> 的類(lèi)型就大于 List<Integer> 的類(lèi)型了逃默,也就實(shí)現(xiàn)了協(xié)變鹃愤。這也就是所謂的“子類(lèi)的泛型是泛型的子類(lèi)”。

同樣完域,下界通配符 <? super T> 可以實(shí)現(xiàn)逆變软吐,如:

public List<? super Integer> fun(){
    List<Object> l = new ArrayList<>();
    return l;
}

上述代碼怎么就實(shí)現(xiàn)逆變了呢?首先吟税,Object > Integer凹耙;另外,從前言我們知道肠仪,函數(shù)返回值類(lèi)型必須大于實(shí)際返回值類(lèi)型肖抱,在這里就是 List<? super Integer> > List<Object>,和 Object > Integer 剛好相反异旧。也就是說(shuō)意述,經(jīng)過(guò)泛型變化后,Object 和 Integer 的類(lèi)型關(guān)系翻轉(zhuǎn)了吮蛹,這就是逆變荤崇,而實(shí)現(xiàn)逆變的就是下界通配符 <? super T>。

從上面可以看出潮针,<? extends T> 中的上界是 T术荤,也就是說(shuō) <? extends T> 所泛指的類(lèi)型都是 T 的子類(lèi)或 T 本身,所以 T 大于 <? extends T> 每篷。<? super T> 中的下界是 T瓣戚,也就是說(shuō) <? super T> 所泛指的類(lèi)型都是 T 的父類(lèi)或 T 本身,所以 <? super T> 大于 T焦读。

雖然 Java 使用通配符解決了泛型的協(xié)變與逆變的問(wèn)題子库,但是由于很多講到泛型的文章都晦澀難懂,曾經(jīng)讓我一度感慨這 tm 到底是什么玩意矗晃?直到我在 stackoverflow 上發(fā)現(xiàn)了通俗易懂的解釋(是的刚照,前文大部分內(nèi)容都來(lái)自于 stackoverflow 中大神的解釋),才終于了然喧兄。其實(shí)只要抓住賦值語(yǔ)句左邊類(lèi)型必須大于右邊類(lèi)型這個(gè)關(guān)鍵點(diǎn)一切就都很好懂了无畔。

PECS

PECS 準(zhǔn)則即 Producer Extends Consumer Super,生產(chǎn)者使用上界通配符吠冤,消費(fèi)者使用下界通配符浑彰。直接看這句話可能會(huì)讓人很疑惑,所以我們追本溯源來(lái)看看為什么會(huì)有這句話拯辙。

首先郭变,我們寫(xiě)一個(gè)簡(jiǎn)單的泛型類(lèi):

public class Container<T> {
    private T item;

    public void set(T t) { 
        item = t;
    }

    public T get() {
        return item;
    }
}

然后寫(xiě)出如下代碼:

Container<Object> c = new Container<String>(); // (1)編譯報(bào)錯(cuò)

Container<? extends Object> c = new Container<String>(); // (2)編譯通過(guò)
c.set("sss"); // (3)編譯報(bào)錯(cuò)
Object o = c.get();// (4)編譯通過(guò)

代碼 (1)颜价,Container<Object> c = new Container<String>(); 編譯報(bào)錯(cuò),因?yàn)榉盒褪遣恍妥兊乃弑簦?Container<String> 并不是 Container<Object> 的子類(lèi)型周伦,所以無(wú)法賦值。

代碼 (2)未荒,加了上界通配符以后专挪,支持泛型協(xié)變,Container<String> 就成了 Container<? extends Object> 的子類(lèi)型片排,所以編譯通過(guò)寨腔,可以賦值。

既然代碼 (2) 通過(guò)編譯率寡,那代碼 (3) 為什么會(huì)報(bào)錯(cuò)呢迫卢?因?yàn)榇a (3) 嘗試把 String 類(lèi)型賦值給 <? extends Object> 類(lèi)型。顯然冶共,編譯器只知道 <? extends Object> 是 Obejct 的某一個(gè)子類(lèi)型乾蛤,但是具體是哪一個(gè)并不知道,也許并不是 String 類(lèi)型捅僵,所以不能直接將 String 類(lèi)型賦值給它家卖。

從上面可以看出,對(duì)于使用了 <? extends T> 的類(lèi)型命咐,是不能寫(xiě)入元素的,不然就會(huì)像代碼 (3) 處一樣編譯報(bào)錯(cuò)谐岁。

但是可以讀取元素醋奠,比如代碼 (4) 。該類(lèi)型只能讀取元素伊佃,這就是所謂的“生產(chǎn)者”窜司,即只能從中讀取元素的就是生產(chǎn)者,生產(chǎn)者就使用 <? extends T> 通配符航揉。

消費(fèi)者同理塞祈,代碼如下:

Container<String> c = new Container<Object>(); // (1)編譯報(bào)錯(cuò)

Container<? super String> c = new Container<Object>(); // (2)編譯通過(guò)
 c.set("sss");// (3) 編譯通過(guò)
 String s = c.get();// (4) 編譯報(bào)錯(cuò)

代碼 (1) 編譯報(bào)錯(cuò),因?yàn)榉盒筒恢С帜孀兯俊6揖退悴欢盒鸵樾剑@個(gè)代碼的形式一眼看起來(lái)也是錯(cuò)的。

代碼 (2) 編譯通過(guò)媳友,因?yàn)榧恿?<? super T> 通配符后斯议,泛型逆變。

代碼 (3) 編譯通過(guò)醇锚,它把 String 類(lèi)型賦值給 <? super String>哼御,<? super String> 泛指 String 的父類(lèi)或 String坯临,所以這是可以通過(guò)編譯的。

代碼 (4) 編譯報(bào)錯(cuò)恋昼,因?yàn)樗鼑L試把 <? super String> 賦值給 String看靠,而 <? super String> 大于 String,所以不能賦值液肌。事實(shí)上挟炬,編譯器完全不知道該用什么類(lèi)型去接受 c.get() 的返回值,因?yàn)樵诰幾g器眼里 <? super String> 是一個(gè)泛指的類(lèi)型矩屁,所有 String 的父類(lèi)和 String 本身都有可能辟宗。

同樣從上面代碼可以看出,對(duì)于使用了 <? super T> 的類(lèi)型吝秕,是不能讀取元素的泊脐,不然就會(huì)像代碼 (4) 處一樣編譯報(bào)錯(cuò)。但是可以寫(xiě)入元素烁峭,比如代碼 (3)容客。該類(lèi)型只能寫(xiě)入元素,這就是所謂的“消費(fèi)者”约郁,即只能寫(xiě)入元素的就是消費(fèi)者缩挑,消費(fèi)者就使用 <? super T> 通配符。

綜上鬓梅,這就是 PECS 原則供置。

kotlin 中的協(xié)變與逆變

kotlin 拋棄了 Java 中的通配符,轉(zhuǎn)而使用了聲明處型變類(lèi)型投影绽快。

聲明處型變

首先讓我們回頭看看 Container 的定義:

public class Container<T> {
    private T item;

    public void set(T t) { 
        item = t;
    }

    public T get() {
        return item;
    }
}

在某些情況下芥丧,我們只會(huì)使用 Container<? extends T> 或者 Container<? super T> ,意味著我們只使用 Container 作為生產(chǎn)者或者 Container 作為消費(fèi)者坊罢。

既然如此续担,那我們?yōu)槭裁匆诙x Container 這個(gè)類(lèi)的時(shí)候要把 get 和 set 都定義好呢?試想一下活孩,如果一個(gè)類(lèi)只有消費(fèi)者的作用物遇,那定義 get 方法完全是多余的。

反過(guò)來(lái)說(shuō)憾儒,如果一個(gè)泛型類(lèi)只有生產(chǎn)者方法询兴,比如下面這個(gè)例子(來(lái)自 kotlin 官方文檔):

// Java
interface Source<T> {
  T nextT(); // 只有生產(chǎn)者方法
}
// Java
void demo(Source<String> strs) {
  Source<Object> objects = strs; // !F鹬骸蕉朵!在 Java 中不允許,要使用上界通配符 <? extends Object>
  // ……
}

Source 類(lèi)型的變量中存儲(chǔ) Source 實(shí)例的引用是極為安全的——沒(méi)有消費(fèi)者-方法可以調(diào)用阳掐。然而 Java 依然不讓我們直接賦值始衅,需要使用上界通配符冷蚂。

但是這是毫無(wú)意義的,使用通配符只是把類(lèi)型變得更復(fù)雜汛闸,并沒(méi)有帶來(lái)額外的價(jià)值蝙茶,因?yàn)槟苷{(diào)用的方法還是只有生產(chǎn)者方法。但編譯器并不知道這回事诸老。

所以隆夯,如果我們能在使用之前確定一個(gè)類(lèi)是生產(chǎn)者還是消費(fèi)者,那在定義類(lèi)的時(shí)候直接聲明它的角色豈不美哉别伏?

這就是 kotlin 的聲明處型變蹄衷,直接在類(lèi)聲明的時(shí)候,定義它的型變行為厘肮。

比如:

class Container<out T> { // (1)
    private  var item: T? = null 
        
    fun get(): T? = item
}

val c: Container<Any> = Container<String>()// (2)編譯通過(guò)愧口,因?yàn)?T 是一個(gè) out-參數(shù)

(1) 處直接使用 <out T> 指定 T 類(lèi)型只能出現(xiàn)在生產(chǎn)者的位置上。雖然多了一些限制类茂,但是耍属,在 kotlin 編譯器直到了 T 的角色以后,就可以像 (2) 處一樣將 Container<String> 直接賦值給 Container<Any>巩检,好像泛型直接可以協(xié)變了一樣厚骗,而不需要再使用 Java 當(dāng)中的通配符 <? extends String>。

同樣的兢哭,對(duì)于消費(fèi)者來(lái)說(shuō)领舰,

class Container<in T> { // (1) 
    private  var item: T? = null 
     fun set(t: T) {
        item = t
    }
}

val c: Container<String> = Container<Any>() // (2) 編譯通過(guò),因?yàn)?T 是一個(gè) in-參數(shù)

代碼 (1) 處使用 <in T> 指定 T 類(lèi)型只能出現(xiàn)在消費(fèi)者的位置上。代碼 (2) 可以編譯通過(guò)迟螺, Any > String冲秽,但是 Container<String> 可以被 Container<Any> 賦值,意味著 Container<String> 大于 Container<Any> 煮仇,即它看上去就像 T 直接實(shí)現(xiàn)了泛型逆變劳跃,而不需要借助 <? super String> 通配符來(lái)實(shí)現(xiàn)逆變谎仲。如果是 Java 代碼浙垫,則需要寫(xiě)成 Container<? super String> c = new Container<Object>();

這就是聲明處型變郑诺,在類(lèi)聲明的時(shí)候使用 out 和 in 關(guān)鍵字夹姥,在使用時(shí)可以直接寫(xiě)出泛型型變的代碼。

而 Java 在使用時(shí)必須借助通配符才能實(shí)現(xiàn)泛型型變辙诞,這是使用處型變辙售。

類(lèi)型投影

有時(shí)一個(gè)類(lèi)既可以作生產(chǎn)者又可以作消費(fèi)者,這種情況下飞涂,我們不能直接在 T 前面加 in 或者 out 關(guān)鍵字旦部。比如:

class Container<T> {
    private  var item: T? = null
    
    fun set(t: T祈搜?) {
        item = t
    }

    fun get(): T? = item
}

考慮這個(gè)函數(shù):

fun copy(from: Container<Any>, to: Container<Any>) {
    to.set(from.get())
}

當(dāng)我們實(shí)際使用該函數(shù)時(shí):

val from = Container<Int>()
val to = Container<Any>()
copy(from, to) // 報(bào)錯(cuò),from 是 Container<Int> 類(lèi)型士八,而 to 是 Container<Any> 類(lèi)型
image-20201011204330187.png

這樣使用的話容燕,編譯器報(bào)錯(cuò),因?yàn)槲覀儼褍蓚€(gè)不一樣的類(lèi)型做了賦值婚度。用 kotlin 官方文檔的話說(shuō)蘸秘,copy 函數(shù)在”干壞事“, 它嘗試寫(xiě)一個(gè) Any 類(lèi)型的值給 from蝗茁, 而我們用 Int 類(lèi)型來(lái)接收這個(gè)值醋虏,如果編譯器不報(bào)錯(cuò),那么運(yùn)行時(shí)將會(huì)拋出一個(gè) ClassCastException 異常哮翘。

所以應(yīng)該怎么辦颈嚼?直接防止 from 被寫(xiě)入就可以了!

將 copy 函數(shù)改為如下所示:

fun copy(from: Container<out Any>, to: Container<Any>) { // 給 from 的類(lèi)型加了 out
    to.set(from.get())
}
val from = Container<Int>()
val to = Container<Any>()
copy(from, to) // 不會(huì)再報(bào)錯(cuò)了

這就是類(lèi)型投影:from 是一個(gè)類(lèi)受限制的(投影的)Container 類(lèi)忍坷,我們只能把它當(dāng)作生產(chǎn)者來(lái)使用粘舟,它只能調(diào)用 get() 方法。

同理佩研,如果 from 的泛型是用 in 來(lái)修飾的話柑肴,則 from 只能被當(dāng)作消費(fèi)者使用,它只能調(diào)用 set() 方法旬薯,上述代碼就會(huì)報(bào)錯(cuò):

fun copy(from: Container<in Any>, to: Container<Any>) { // 給 from 的類(lèi)型加了 in
    to.set(from.get())
}
val from = Container<Int>()
val to = Container<Any>()
copy(from, to) //  報(bào)錯(cuò)
image-20201011210124162.png

其實(shí)從上面可以看到晰骑,類(lèi)型投影和 Java 的通配符很相似,也是一種使用時(shí)型變绊序。

為什么要這么設(shè)計(jì)硕舆?

為什么 Java 的數(shù)組是默認(rèn)型變的,而泛型默認(rèn)不型變呢骤公?其實(shí) kolin 的泛型默認(rèn)也是不型變的抚官,只是使用 out 和 in 關(guān)鍵字讓它看起來(lái)像泛型型變。

為什么這么設(shè)計(jì)呢阶捆?為什么不默認(rèn)可型變呢凌节?

在 stackoverflow 上找到了答案,參考:https://stackoverflow.com/questions/18666710/why-are-arrays-covariant-but-generics-are-invariant

Java 和 C# 早期都是沒(méi)有泛型特性的洒试。

但是為了支持程序的多態(tài)性倍奢,于是將數(shù)組設(shè)計(jì)成了協(xié)變的。因?yàn)閿?shù)組的很多方法應(yīng)該可以適用于所有類(lèi)型元素的數(shù)組垒棋。

比如下面兩個(gè)方法:

boolean equalArrays (Object[] a1, Object[] a2);
void shuffleArray(Object[] a);

第一個(gè)是比較數(shù)組是否相等卒煞;第二個(gè)是打亂數(shù)組順序。

語(yǔ)言的設(shè)計(jì)者們希望這些方法對(duì)于任何類(lèi)型元素的數(shù)組都可以調(diào)用叼架,比如我可以調(diào)用 shuffleArray(String[] s) 來(lái)把字符串?dāng)?shù)組的順序打亂畔裕。

出于這樣的考慮衣撬,在 Java 和 C# 中,數(shù)組設(shè)計(jì)成了協(xié)變的扮饶。

然而淮韭,對(duì)于泛型來(lái)說(shuō),卻有以下問(wèn)題:

// Illegal code - because otherwise life would be Bad
List<Dog> dogs = new List<Dog>();
List<Animal> animals = dogs; // Awooga awooga
animals.add(new Cat());// (1)
Dog dog = dogs.get(0); //(2) This should be safe, right?

如果上述代碼可以通過(guò)編譯贴届,即 List<Dog> 可以賦值給 List<Animal>靠粪,List 是協(xié)變的,那么我完全可以往 List<Dog> 中 add 一個(gè) Cat()毫蚓,如代碼 (1) 處占键。這樣就有可能造成代碼 (2) 處的接收者 dog 和 dogs.get(0) 的類(lèi)型不匹配的問(wèn)題。會(huì)引發(fā)運(yùn)行時(shí)的異常元潘。所以 Java 在編譯期就要阻止這種行為畔乙,把泛型設(shè)計(jì)為默認(rèn)不型變的。

總結(jié)

1翩概、Java 泛型默認(rèn)不型變牲距,所以 List<String> 不是 List<Object> 的子類(lèi)。如果要實(shí)現(xiàn)泛型型變钥庇,則需要 <? extends T> 與 <? super T> 通配符牍鞠,這是一種使用處型變的方法。使用 <? extends T> 通配符意味著該類(lèi)是生產(chǎn)者评姨,只能調(diào)用 get(): T 之類(lèi)的方法难述。而使用 <? super T> 通配符意味著該類(lèi)是消費(fèi)者,只能調(diào)用 set(T t)吐句、add(T t) 之類(lèi)的方法。

2嗦枢、Kotlin 泛型其實(shí)默認(rèn)也是不型變的攀芯,只不過(guò)使用 out 和 in 關(guān)鍵字在類(lèi)聲明處型變,可以達(dá)到在使用處看起來(lái)像直接型變的效果文虏。但是這樣會(huì)限制類(lèi)在聲明時(shí)只能要么作為生產(chǎn)者侣诺,要么作為消費(fèi)者。

使用類(lèi)型投影可以避免類(lèi)在聲明時(shí)被限制择葡,但是在使用時(shí)要使用 out 和 in 關(guān)鍵字指明這個(gè)時(shí)刻類(lèi)所充當(dāng)?shù)慕巧窍M(fèi)者還是生產(chǎn)者紧武。類(lèi)型投影也是一種使用處型變的方法剃氧。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末敏储,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子朋鞍,更是在濱河造成了極大的恐慌已添,老刑警劉巖妥箕,帶你破解...
    沈念sama閱讀 216,372評(píng)論 6 498
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異更舞,居然都是意外死亡畦幢,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,368評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門(mén)缆蝉,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)宇葱,“玉大人,你說(shuō)我怎么就攤上這事刊头∈蚯疲” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 162,415評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵原杂,是天一觀的道長(zhǎng)印颤。 經(jīng)常有香客問(wèn)我,道長(zhǎng)穿肄,這世上最難降的妖魔是什么年局? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,157評(píng)論 1 292
  • 正文 為了忘掉前任,我火速辦了婚禮咸产,結(jié)果婚禮上矢否,老公的妹妹穿的比我還像新娘。我一直安慰自己脑溢,他們只是感情好兴喂,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,171評(píng)論 6 388
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著焚志,像睡著了一般衣迷。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上酱酬,一...
    開(kāi)封第一講書(shū)人閱讀 51,125評(píng)論 1 297
  • 那天壶谒,我揣著相機(jī)與錄音,去河邊找鬼膳沽。 笑死汗菜,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的挑社。 我是一名探鬼主播陨界,決...
    沈念sama閱讀 40,028評(píng)論 3 417
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼痛阻!你這毒婦竟也來(lái)了菌瘪?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 38,887評(píng)論 0 274
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎俏扩,沒(méi)想到半個(gè)月后糜工,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,310評(píng)論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡录淡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,533評(píng)論 2 332
  • 正文 我和宋清朗相戀三年捌木,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片嫉戚。...
    茶點(diǎn)故事閱讀 39,690評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡刨裆,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出彬檀,到底是詐尸還是另有隱情崔拥,我是刑警寧澤,帶...
    沈念sama閱讀 35,411評(píng)論 5 343
  • 正文 年R本政府宣布凤覆,位于F島的核電站链瓦,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏盯桦。R本人自食惡果不足惜慈俯,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,004評(píng)論 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望拥峦。 院中可真熱鬧贴膘,春花似錦、人聲如沸略号。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,659評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)玄柠。三九已至突梦,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間羽利,已是汗流浹背宫患。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 32,812評(píng)論 1 268
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留这弧,地道東北人娃闲。 一個(gè)月前我還...
    沈念sama閱讀 47,693評(píng)論 2 368
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像匾浪,于是被迫代替她去往敵國(guó)和親皇帮。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,577評(píng)論 2 353