開始切換到 Kotlin: 谷歌工程師給初學(xué)者的知識點(diǎn)總結(jié)

image

2019 年的 I/O 大會(huì)上,我們曾宣布 Kotlin 將會(huì)是 Android 應(yīng)用開發(fā)的首選語言尖淘,但是奕锌,部分開發(fā)者們反饋仍不清楚如何切換到 Kotlin,如果團(tuán)隊(duì)中沒有人熟悉 Kotlin村生,一開始直接使用 Kotlin 進(jìn)行項(xiàng)目開發(fā)還是會(huì)令人生畏惊暴。

在 Android Studio Profiler 團(tuán)隊(duì)內(nèi)部,我們是通過幾個(gè)步驟克服了這個(gè)問題趁桃,第一步是要求所有的單元測試使用 Kotlin 編寫辽话。這么做有效避免了我們犯的任何微小錯(cuò)誤直接影響到生產(chǎn)環(huán)境中的代碼,因?yàn)閱卧獪y試與生產(chǎn)環(huán)境的代碼是分開的镇辉。

我收集了我們團(tuán)隊(duì)在歷次 Code Review 中遇到過的常見問題并整理出了這篇文章屡穗,希望這篇文章對廣大 Android 社區(qū)的朋友們有所幫助。

注意: 本文的目標(biāo)讀者是 Kotlin 的初學(xué)者忽肛,如果您的團(tuán)隊(duì)已經(jīng)熟練使用 Kotlin 進(jìn)行項(xiàng)目開發(fā)村砂,本文對您的幫助可能不大。但如果您覺得我們遺漏了一些應(yīng)該被提及的內(nèi)容屹逛,請?jiān)诒疚牧粞詤^(qū)留言告訴我們础废。

IDE 功能: 把 Java 文件轉(zhuǎn)換成 Kotlin 文件

如果您使用 Android Studio 開發(fā)程序,學(xué)習(xí) Kotlin 的最簡單方法是使用 Java 語言編寫單元測試罕模,然后在Android Studio 的菜單欄中點(diǎn)擊 Code -> Convert Java File to Kotlin File 按鈕將 Java 文件轉(zhuǎn)換成 Kotlin 文件评腺。

image

這個(gè)操作可能會(huì)提示您 "Some code in the rest of your project may require corrections after performing this conversion. Do you want to find such code and correct it too?",它的意思是說項(xiàng)目中的其他代碼可能會(huì)受到此次轉(zhuǎn)換的影響淑掌,而且有可能會(huì)導(dǎo)致錯(cuò)誤蒿讥,請問是否需要定位出錯(cuò)的代碼芋绸,并對相關(guān)代碼進(jìn)行修改摔敛。我建議選擇 "No",這樣您就可以將代碼的修改集中在一個(gè)文件上马昙。

雖然通過此操作生成了 Kotlin 代碼,但是轉(zhuǎn)換出來的代碼還是有很多提升空間攒暇,下面各個(gè)章節(jié)介紹針對自動(dòng)生成代碼的優(yōu)化技巧敢伸,這是我們通過幾十次的 Code Review 總結(jié)出來的技巧。當(dāng)然尾序,Kotlin 的功能遠(yuǎn)不止下面討論的內(nèi)容每币,本文會(huì)聚焦在于我們團(tuán)隊(duì)遇到過的問題琢歇,特別是可復(fù)現(xiàn)的問題上。

兩種語言的高階對比

Java 與 Kotlin 在高階角度來看是非常相似的揭保,下面是分別使用 Java 與 Kotlin 編寫的基本單元測試代碼秸侣。

/// Java
public class ExampleTest {
  @Test
  public void testMethod1() throws Exception {}
  @Test
  public void testMethod2() throws Exception {}
}
/// Kotlin
class ExampleTest {
  @Test
  fun testMethod1() {}
  @Test
  fun testMethod2() {}
}

在 Kotlin 里需要注意:

  • 方法與類的默認(rèn)修飾符類型是 Public
  • 函數(shù)定義中如果返回值是空 (void) 的話可以忽略
  • 沒有檢查性異常

分號是可選項(xiàng)

這個(gè)特性一開始可能會(huì)讓您不適應(yīng)宠互。但是在實(shí)踐中予跌,您不需要有過多的擔(dān)心。您可以按照以前的編程習(xí)慣使用分號频轿,而且不會(huì)影響到代碼的編譯過程,但 IDE 會(huì)自動(dòng)找出這些可刪除的分號并提示您集币。只需要在提交代碼之前刪掉就可以了翠忠。

無論您喜歡與否秽之,Java 已經(jīng)在某些地方早就不使用分號了吃既,如果跟 C++ 對比的話會(huì)更明顯 (因?yàn)?C++ 使用分號的場景更多)。

/// C++
std::thread([this] { this->DoThreadWork(); });
/// Java
new Thread(() -> doThreadWork());
/// Kotlin
Thread { doThreadWork() }

在尾部聲明類型

/// Java
int value = 10;
Entry add(String name, String description)
/// Kotlin
var value: Int = 10
fun add(name: String, description: String): Entry

和分號是可選項(xiàng)類似河质,如果您還沒有習(xí)慣使用這個(gè)特性的話掀鹅,可能會(huì)讓您難以接受媒楼,它與很多人在他們編程生涯中遇到過的類型聲明相比,這里的順序正好相反扔嵌。

但這個(gè)語法帶來的好處是痢缎,如果變量類型是可以自動(dòng)被推測出來的話世澜,此時(shí)可以直接跳過類型聲明。這個(gè)特性在后面的 "省略變量類型" 章節(jié)里有介紹势告。

還有個(gè)好處是可以把更多的注意力放在變量本身而不是它的類型上咱台。而且我發(fā)現(xiàn)在討論代碼的時(shí)候俭驮,類型在后的順序聽起來更自然 (從英文語言角度)春贸。

/// Java
int result;     // 整數(shù)型的變量萍恕,名字叫 "result"
/// Kotlin
var result: Int // 變量名字叫 "result" 允粤,是整數(shù)型

對此語法我想說的最后一件事情是翼岁,雖然一開始您可能感覺不適,但是隨著使用頻率的增加悉患,您慢慢會(huì)習(xí)慣榆俺。

沒有 new 關(guān)鍵字的構(gòu)造函數(shù)

Kotlin 中不需要使用 new 關(guān)鍵字調(diào)用構(gòu)造函數(shù)茴晋。

/// Java
... = new SomeClass();
/// Kotlin
... = SomeClass()

起初這會(huì)讓您覺得漏掉了關(guān)鍵信息 (指創(chuàng)建內(nèi)存的操作),但不需要擔(dān)心揩局。因?yàn)樵?Java 中掀虎,有些函數(shù)會(huì)在您不知情的情況下創(chuàng)建內(nèi)存烹玉。對此,您從來也沒有關(guān)心過 (也不需要關(guān)心)县忌。很多函數(shù)庫甚至還有創(chuàng)建內(nèi)存的靜態(tài)方法继效,比如:

/// Java
Lists.newArrayList();

Kotlin 只是統(tǒng)一了函數(shù)的這種行為。因?yàn)閷σ粋€(gè)函數(shù)來說厉颤,無論分配內(nèi)存與否逼友,這只是它的附加效果而已,并不影響函數(shù)調(diào)用帜乞。

而且它還簡化了創(chuàng)建對象只是為了調(diào)用其方法的寫法。

/// Java
new Thread(...).start(); // 有點(diǎn)奇怪但是是可以這樣寫的
/// Kotlin
Thread(...).start()

可變性與不可變性

在 Java 中變量默認(rèn)是可變的习柠,使用 final 關(guān)鍵字可以使變量為不可變津畸。與之相反必怜,在 Kotlin 中是沒有 final 關(guān)鍵字梳庆。您需要使用 val 關(guān)鍵字指示變量是不可變的卑惜,使用 var 關(guān)鍵字指示變量是可變的露久。

/// Java
private final Project project; // 初始化之后無法再賦值
private Module activeModule;   // 初始化之后可以再賦值
/// Kotlin
private val project: Project     // 初始化之后無法再賦值
private var activeModule: Module // 初始化之后可以再賦值

在 Java 中您可能會(huì)經(jīng)常遇到很多成員變量應(yīng)該是常數(shù),但是卻沒有使用 final 關(guān)鍵字 (忘記加上 final 是容易犯的錯(cuò)誤)征峦。在 Kotlin 中您必須顯式地聲明每個(gè)成員變量的類型消请。如果您一開始不確定該選擇哪種類型,那就默認(rèn)使用 val 類型蛉加,后面有需求變化時(shí)再改為 var缸逃。

順便說一句需频,在 Java 中函數(shù)參數(shù)類型是可變的,但是可以使用 final 關(guān)鍵字修改為不可變户盯。在 Kotlin 中,函數(shù)參數(shù)始終是不可變的莽鸭,它們是被 val 關(guān)鍵字隱式地標(biāo)記為不可變硫眨。

/// Java
public void log(final String message) { … }
/// Kotlin
fun log(message: String) { … } // "message" is immutable

可空性

Kotlin 中取消了 @NotNull 跟 @Nullable 的注解方法。如果變量可以賦值為 null巧号,您只需要在變量類型后面加上一個(gè) "?" 就可以了:

/// Java
@Nullable Project project;
@NotNull String title;
/// Kotlin
val project: Project?
val title: String

在某些情況下丹鸿,當(dāng)您確定某些可以被賦值為 null 的變量不可能是 null棚品,您可以使用 !! 操作符設(shè)置一個(gè)斷言。

/// Kotlin
// 'parse' 可以返回 null门怪,但這條用例總是能夠運(yùn)行
val result = parse("123")!!
// 下面這行是多余的锅纺,因?yàn)?!! 已經(jīng)觸發(fā)斷言了
? assertThat(result).isNotNull()

如果您錯(cuò)誤地使用了 !!囤锉,它有可能會(huì)拋出 NullPointerException 的異常。在單元測試中减拭,這只會(huì)造成測試用例的失敗区丑,但是在生產(chǎn)環(huán)境中,可能會(huì)使程序崩潰可霎,所以要非常小心癣朗。事實(shí)上旺罢,在生產(chǎn)環(huán)境的代碼中有太多的 !! 操作符绢记,可能意味著此處有 "代碼異味" ("代碼異味" 代表這部分代碼可能需要審查或重構(gòu))蠢熄。

在單元測試中签孔,測試用例里使用 !! 操作符是可接受的窘行,原因是當(dāng)假設(shè)不成立的時(shí)候測試用例會(huì)失敗,并且您還可以修復(fù)它但绕。

如果您確定使用 !! 操作符是有意義的惶看,那盡量靠前使用,如下面的用法:

/// Kotlin
val result = parse("...")!!
result.doSomething()
result.doSomethingElse()

下面是錯(cuò)誤用法:

/// Kotlin (自動(dòng)生成)
val result = parse("...")
? result!!.doSomething()
? result!!.doSomethingElse()

可省略變量的類型

在 Java 中會(huì)看到如下寫法的代碼:

/// Java
SomeClass instance1 = new SomeClass();
SomeGeneric<List<String>> instance2 = new SomeGeneric<>();

在 Kotlin 中,類型聲明被認(rèn)為是冗余的操作莹桅,不需要寫兩次:

/// Kotlin
val instance1 = SomeClass()
val instance2 = SomeGeneric<List<String>>()

然而诈泼,我們在使用動(dòng)態(tài)綁定的時(shí)候可能會(huì)需要聲明這些類型:

/// Java
BaseClass instance = new ChildClass(); // 如:List = new ArrayList

在 Kotlin 中使用下面語法達(dá)到同樣目的:

/// Kotlin
val instance: BaseClass = ChildClass()

沒有檢查性異常

不像 Java 那樣煤禽,Kotlin 中的類方法不需要聲明自己的異常類型。因?yàn)樵?Kotlin 中檢查性異常 (Checked Exception) 跟運(yùn)行時(shí)異常 (Runtime Exception) 之間是沒有區(qū)別的瓮孙。

/// Java
public void readFile() throws IOException { … }
/// Kotlin
fun readFile() { … }

但是為了使 Java 可以調(diào)用 Kotlin 的代碼选脊,Kotlin 還是提供 @Throws 注解功能,用于隱式的聲明異常類型偏灿。當(dāng)執(zhí)行 Java → Kotlin 轉(zhuǎn)換時(shí)钝的,IDE 會(huì)保證安全性并且始終包含這類信息铆遭。

/// Kotlin (從 Java 自動(dòng)轉(zhuǎn)換而來)
@Throws(Exception::class)

fun testSomethingImportant() { … }

但您不需要擔(dān)心您的單元測試會(huì)被 Java 類調(diào)用枚荣。因此邢疙,您可以安全地刪除這些類型聲明而且還可以減少代碼行數(shù):

/// Kotlin
fun testSomethingImportant() { … }

Lambda 調(diào)用時(shí)可以省略括號

在 Kotlin 中疟游,如果您想要把一個(gè)閉包賦值給變量,您需要顯式地聲明它的類型:

val sumFunc: (Int, Int) -> Int = { x, y -> x + y }

如果所有類型是可以被推測的蛮原,則可以簡寫成:

{ x, y -> x + y }

舉個(gè)例子:

val intList = listOf(1, 2, 3, 4, 5, 6)
val sum = intList.fold(0, { x, y -> x + y })

需要注意的是儒陨,如果一個(gè)函數(shù)調(diào)用的最后一個(gè)參數(shù)是 lambda 調(diào)用時(shí)笋籽,這時(shí)候可以把閉包寫在函數(shù)括號的外面。

上面代碼等同于如下:

val sum = intList.fold(0) { x, y -> x + y }

有能力做笛园,但并不意味著您應(yīng)該這么做侍芝。有些人會(huì)覺得上面使用 fold 的方法比較奇怪州叠。某些場景下這種語法減少了視覺干擾,特別是函數(shù)的參數(shù)只有一個(gè)閉包時(shí)逆甜。如果我們想統(tǒng)計(jì)偶數(shù)的數(shù)量時(shí)致板,對比如下兩個(gè)用法:

用法一:

intList.filter({ x -> x % 2 == 0 }).count()

用法二:

intList.filter { x -> x % 2 == 0 }.count()

或者對比 Thread 函數(shù)使用,如下兩個(gè)用法:

用法一:

Thread({ doThreadWork() })

用法二:

Thread { doThreadWork() }

無論您喜歡與否错敢,當(dāng)您在 Kotlin 中看到這類用法時(shí)您應(yīng)該知道它是怎么工作的稚茅,Java → Kotlin 轉(zhuǎn)換中也會(huì)用到這種語法。

equals() 方法咽块、 == 與 === 運(yùn)算符

Kotlin 在相等性測試方面不同于 Java欺税。

在 Java 中,== 運(yùn)算符是用于比較兩個(gè)對象的引用是否相同亭罪,它是有別于 equals() 方法应役。盡管從理論上聽起來不錯(cuò)燥筷,在實(shí)踐中開發(fā)者經(jīng)常會(huì)在需要使用 equals 的地方使用了 == 運(yùn)算符。這可能會(huì)引入不易察覺的 Bug袍祖,需要花費(fèi)數(shù)小時(shí)來定位問題谢揪。

在 Kotlin 中 == 運(yùn)算符等同于 equals 方法键耕,唯一的區(qū)別是它還能正確地處理與 null 間的比較屈雄。舉個(gè)例子,null==x 是合法的操作酒奶,但是 null.equals(x) 會(huì)拋出 NullPointerException 異常惋嚎。

如果您想在 Kotlin 中判斷對象引用的相等性站刑,那您可以使用 === 運(yùn)算符,這種語法不容易用錯(cuò)而且還更容易定位問題摆尝。

/// Java
Color first = new Color(255, 0, 255);
Color second = new Color(255, 0, 255);
assertThat(first.equals(second)).isTrue();
assertThat(first == second).isFalse();
/// Kotlin
val first = Color(255, 0, 255)
val second = Color(255, 0, 255)
assertThat(first.equals(second)).isTrue()
assertThat(first == second).isTrue()
assertThat(first === second).isFalse()

當(dāng)您使用 Kotlin 的時(shí)候翰意,大部分情況下會(huì)使用 == 運(yùn)算符,因?yàn)?=== 運(yùn)算符的應(yīng)用場景相對來說比較少讯检。需要指出的是,Java → Kotlin 轉(zhuǎn)換器始終會(huì)把 Java 中的 == 運(yùn)算符轉(zhuǎn)換成 Kotlin 中的 === 運(yùn)算符围段。出于代碼可讀性跟功能意圖考慮挡毅,在必要時(shí)您應(yīng)該把 === 運(yùn)算符恢復(fù)成 == 運(yùn)算符。對比枚舉類型時(shí)經(jīng)常會(huì)遇到上面所說的情況段磨,如下所示:

/// Java
if (day == DayOfWeek.MONDAY) { … }

/// Kotlin (從 Java 自動(dòng)轉(zhuǎn)換而來)
? if (day === DayOfWeek.MONDAY) { … }

/// Kotlin
if (day == DayOfWeek.MONDAY) { … }

移除成員變量的前綴

在 Java 中苹支,對私有變量編寫成對的 getter 與 setter 方法是很常見的做法误阻,而且很多 Java 代碼給成員變量命名時(shí)加上了前綴,有點(diǎn)像是匈牙利命名法寻定。

/// Java
private String myName;
// 或者 private String mName;
// 或者 private String _name;
public String getName() { … }
public void setName(String name) { … }

這種前綴適合給變量做標(biāo)記狼速,代表著該變量只在類的內(nèi)部可見卦停。而且還容易區(qū)分是類的內(nèi)部成員變量還是通過函數(shù)參數(shù)傳遞進(jìn)來的變量。

在 Kotlin 中僵芹,成員變量與 getter/setters 方法被整合成同一個(gè)概念拇派。

/// Kotlin
class User {
 val id: String   // 代表成員變量與 getter 方法
 var name: String // 代表成員變量與 getter 和 setter 方法
}

當(dāng)您使用自動(dòng)轉(zhuǎn)換功能時(shí),Java 中的成員變量前綴有時(shí)候會(huì)被保留下來桐腌,帶來的隱患是曾經(jīng)隱藏在內(nèi)部類中的實(shí)現(xiàn)細(xì)節(jié)有可能會(huì)被 public 接口暴露出來苟径。

/// Kotlin (從 Java 自動(dòng)轉(zhuǎn)換而來)
class User {
? val myId: String
? var myName: String
}

為了防止前綴帶來的實(shí)現(xiàn)細(xì)節(jié)的暴露棘街,建議您養(yǎng)成移除前綴的習(xí)慣。

有時(shí)候閱讀沒有前綴的成員變量代碼時(shí)候會(huì)比較費(fèi)勁石挂,尤其是使用網(wǎng)頁版的 Code Review 工具 (比如在很長的類中閱讀很長的函數(shù))险污。不過當(dāng)您使用 IDE 閱讀代碼時(shí)候,可以通過語法高亮功能很清楚地知道哪些是成員變量拯腮,哪些是函數(shù)參數(shù)动壤。您可以通過取消前綴來編寫目的更為聚焦的函數(shù)與類淮逻,以便養(yǎng)成更好的編程習(xí)慣。

結(jié)束語

希望本文章有助于您開始學(xué)習(xí) Kotlin哼丈。您從編寫 Java 開始筛严,使用自動(dòng)轉(zhuǎn)換功能將 Java 轉(zhuǎn)換成 Kotlin。這時(shí)候您會(huì)編寫 Java 風(fēng)格的 Kotlin 代碼髓抑,隨著練習(xí)优幸,不久之后您將會(huì)像專家那樣熟練地編寫 Kotlin 代碼了网杆。

這篇文章只是簡單介紹了 Kotlin 的使用。它的目的在于向那些沒有時(shí)間學(xué)習(xí)但需要將測試用例跑起來的開發(fā)者們介紹 Kotlin 的基本概念與語法队秩。

當(dāng)然昼浦,本文并沒有涵蓋您需要知道的一切关噪。為此,請參考學(xué)習(xí)官方的 Kotlin 文檔:

語言參考教程非常有用建钥,它涵蓋了 Kotlin 的所有知識點(diǎn)虐沥,而且難度適中。

互動(dòng)教程提供了學(xué)習(xí)編程語言的平臺(tái)镐依,還包含了一系列練習(xí)題用于驗(yàn)證您所學(xué)到的知識點(diǎn)是否正確盯荤。

最后,為了將您的代碼重構(gòu)到 Kotlin宏粤,請嘗試我們?yōu)槟鷾?zhǔn)備的 Codelab —— "重構(gòu)為 Kotlin"灼卢,它包含了本文中介紹過的內(nèi)容和其他方面的更多內(nèi)容。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末崇堰,一起剝皮案震驚了整個(gè)濱河市海诲,隨后出現(xiàn)的幾起案子檩互,更是在濱河造成了極大的恐慌闸昨,老刑警劉巖薄风,帶你破解...
    沈念sama閱讀 217,826評論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件遭赂,死亡現(xiàn)場離奇詭異撇他,居然都是意外死亡狈蚤,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,968評論 3 395
  • 文/潘曉璐 我一進(jìn)店門僻弹,熙熙樓的掌柜王于貴愁眉苦臉地迎上來蹋绽,“玉大人筋蓖,你說我怎么就攤上這事◎伎梗” “怎么了瓮下?”我有些...
    開封第一講書人閱讀 164,234評論 0 354
  • 文/不壞的土叔 我叫張陵讽坏,是天一觀的道長。 經(jīng)常有香客問我迷捧,道長漠秋,這世上最難降的妖魔是什么抵屿? 我笑而不...
    開封第一講書人閱讀 58,562評論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮肥荔,結(jié)果婚禮上朝群,老公的妹妹穿的比我還像新娘。我一直安慰自己誉帅,他們只是感情好右莱,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,611評論 6 392
  • 文/花漫 我一把揭開白布慢蜓。 她就那樣靜靜地躺著,像睡著了一般氛悬。 火紅的嫁衣襯著肌膚如雪耘柱。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,482評論 1 302
  • 那天镜遣,我揣著相機(jī)與錄音悲关,去河邊找鬼娄柳。 笑死,一個(gè)胖子當(dāng)著我的面吹牛讶舰,可吹牛的內(nèi)容都是我干的需了。 我是一名探鬼主播,決...
    沈念sama閱讀 40,271評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼鹅颊,長吁一口氣:“原來是場噩夢啊……” “哼堪伍!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起帝雇,我...
    開封第一講書人閱讀 39,166評論 0 276
  • 序言:老撾萬榮一對情侶失蹤尸闸,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后苞尝,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體宦芦,經(jīng)...
    沈念sama閱讀 45,608評論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡调卑,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,814評論 3 336
  • 正文 我和宋清朗相戀三年令野,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片聊浅。...
    茶點(diǎn)故事閱讀 39,926評論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡低匙,死狀恐怖碳锈,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情强重,我是刑警寧澤贸人,帶...
    沈念sama閱讀 35,644評論 5 346
  • 正文 年R本政府宣布艺智,位于F島的核電站,受9級特大地震影響十拣,放射性物質(zhì)發(fā)生泄漏志鹃。R本人自食惡果不足惜曹铃,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,249評論 3 329
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望埠胖。 院中可真熱鬧,春花似錦非竿、人聲如沸谋竖。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,866評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽零聚。三九已至些侍,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間岗宣,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,991評論 1 269
  • 我被黑心中介騙來泰國打工胁住, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留措嵌,地道東北人芦缰。 一個(gè)月前我還...
    沈念sama閱讀 48,063評論 3 370
  • 正文 我出身青樓,卻偏偏與公主長得像浪规,于是被迫代替她去往敵國和親或听。 傳聞我的和親對象是個(gè)殘疾皇子誉裆,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,871評論 2 354

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