學習Kotlin,看這一篇就夠了

人生苦短,要用Kotlin

這是一種對程序猿更為友好的語言,可以減少開發(fā)者的工作量,原本由開發(fā)者干的事情,其實很多都可以由編譯器實現(xiàn)了,這是一種更為高級的語言。Java雖然嚴謹,但卻過于繁瑣,太啰嗦了,一個小事情卻要寫大量的代碼,而且有些代碼又是非常機械式的,在實際編碼過程中都是用IDE來自動生成。Java,C,C++,Object C這些都是上世紀的編程語言。

Kotlin img

現(xiàn)在到了新時代了,編程也發(fā)展了很多,像lambda表達式,函數(shù)式編程,等等一些新的概念和范式在涌現(xiàn)。所以就有了新時代的編程語言,像水果的Swift,Groovy,Scala,以及Java陣營的Kotlin。Kotlin是新一代的編程語言,與Java完美融合,簡潔,方便,可以大大提高程序可讀性,特別是對于Android開發(fā)者來說。水果推出了Swift以解放水果平臺的開發(fā)者,而Kotlin就是來解放Android開發(fā)者的。

雖然說Kotlin可以用在任何可以用Java的地方,但目前主要就是兩大領域服務端,以及Android應用開發(fā),特別是有了Google官方的支持,所以Kotlin對于Android開發(fā)者的意義更為重大,身為一個Android猿,是一定要學習一下這門現(xiàn)代的編程語言的,因為當你學過了之后 ,你會發(fā)現(xiàn),之前寫的代碼都是在浪費生命。

Development environment setup

有三種方式

命令行

其實,這是最好的方式,因為配置起來非常的方便。到官網(wǎng)去下載編譯器,解壓,然后把kotlinc/bin/放到PATH環(huán)境變量里面,就可以了。如果要配置Vim,還需要安裝一下插件,大神們早就把插件準備好了,只需要下載,然后按照官方方法安裝即可,其實就是把解壓后的東西拷貝到相應的目錄里面就好了。

Idea IntellJ

這個看官方文檔就可以了,孤未親測,如遇困難請自行Google。

Android Studio

因為Kotlin官已支持了Android Studio,而Google也支持了,總而言之就是在Android Studio中可以直接使用Kotlin。所以, Android Stuido 3.0以后的版本無需特殊配置,就可以用例Kotlin了。

對于剛開始學習Kotlin而言呢,孤推薦使用命令行的方式,而不要使用Android Studio,特別是直接創(chuàng)建一個基于Kotlin的Android項目,因為此時對語言還不夠熟悉,直接上項目,會迷失在項目配置,frameworks以及語言基礎之中。剛學習一門語言的時候要先學習基本的語法以及語言本身的特性,這最好先繞開框架和項目,會更容易上手一些。

Hello world

這是所有編程語言的入門必學課程,目的是讓學習者快速的體驗一下一門語言,我們也不用多想,照著一個字母,一個字母的把示例敲進去就好了:

  1. 選擇喜歡的文本編輯器,如Vim hello.kt,Kotlin的文件擴展名是*.kt,我們遵循就好。
  2. 一字不差的敲進去:
package hello

fun main(args: Array<String>) {
    println("Hello, world")
}

然后,保存文件

  1. 回到命令行,編譯源碼,如果一切順利會得到一個叫hello.jar的文件,這就是kotlin的最終輸出,也就是它的目標文件.
kotlinc hello.kt -include-runtime -d hello.jar
  1. 運行,這里跟Kotlin其實已經(jīng)沒啥關系了,因為經(jīng)過編譯得到的是一個標準的Jar文件,像運行其他jar一樣運行就好了:
java -jar hello.jar

就會得到輸出Hello, world到此,第一個Kotlin程序已經(jīng)完成,是不是很酷,已經(jīng)迫不及待的想深入學習了!往下看吧。

The basics

語句結構

一行一個語句(先不糾結語句與表達式的區(qū)別),不用加分號,不用打分號,光這個就可以節(jié)省多少時間呢?是不是感覺人生都浪費在了分號上面。如果想在一行寫多個語句,前面的要加上分號。

縮進規(guī)則與Java一致,用四個空格,也可以用tab,或者不加縮進,只要沒人打你。

語句塊需要加上花括號{}。總之,語句結構與Java很類似。

變量

用var來聲明變量,用val來聲明常量,因為Kotlin是靜態(tài)強類型語言(也就是說每個變量在編譯的時候必須知道類型)聲明時需要帶上類型,方法是在變量名的后面加冒號,空格跟上類型名字,與Pascal差不多。如果聲明時直接定義,則可以不用指定類型,編譯器會根據(jù)定義表達式來推測它的類型。示例:

var str: String
val i: Int
var str = "Hello, world"

語句和表達式

主要想說一下語句和表達式的區(qū)別,簡單來說就是表達式是有值的,可以放在變量賦值的右邊,而語句是沒有值的,不能放在賦值的右邊

基本運算

不多說了,跟Java一樣

注釋

這個跟Java也一樣:
// 單行注釋
/* / 多行注釋
/
* */ documentation

函數(shù)

以fun關鍵字來定義一個函數(shù)格式為:fun 函數(shù)名(參數(shù)): 返回類型 {函數(shù)體},如:

fun foo(name: String): Int {
   return name.length()
}

命名參數(shù)和默認值,調(diào)用函數(shù)時可以把參數(shù)的名字帶上,以增加可讀性。聲明函數(shù)時可以用默認值 ,以更好的支持函數(shù)的重載。如:

fun foo(name: String, number: Int = 42, toUpper: Boolean = false): String {}

使用時,可以指定參數(shù)的名字:

foo("a)
foo("b", number = 1)
foo("c", toUpper = true)
foo(name = "d", number = 2, toUpper = false)

表達式體如果一個函數(shù)體內(nèi)只有一個表達式,且有返回值時,那么,可以直接把返回值放在函數(shù) 的后面,如:

fun foo(name: String): String = name.toUpperCase()

甚至還可以把返回類型的聲明給省略掉,如:

fun foo(name: String) = name.toUpperCase()

跟Java不一樣的是,Kotlin的函數(shù)可以聲明為toplevel也就是跟class一個級別,也就是說不必非放在類里面,也就是說跟C和C++是類似的。此外,還可以函數(shù)賦值給一個變量,這個變量就像其他變量一樣。

類與對象

類的聲明與對象創(chuàng)建

用class來聲明一個類型,用:來繼承父類或者實現(xiàn)接口,不需要使用new來創(chuàng)建對象:

class Person {
   var name: String
   var age: Int
}

假如,一個類,是空的,沒有內(nèi)容,那么花括號{}是可以省略的:

class Person

創(chuàng)建對象:

var someone = Person()

Primary constructor

構造方法,有所謂的primary constructor,可以直接寫在類名的后面:

class Person constructor(name: String)

一般情況下,constructor 可以省略掉:

class Person(name: String)

初始化塊因為primary constructor不能包含代碼,所以,想要做些初始化工作就可以放在初始化塊里面(initializer block),也可以在定義屬性時直接使用:

class Person(name: String) {
    var firstName: String = name
    init {
        println("First initializer block that prints ${name}")
    }
}

一般情況下,如果聲明的屬性變量在primary constructor中都有賦值(通過initializer block)的話,可以有更簡潔的表達方式:

class Person(var name: String, var age: Int)

這相當于:

class Person(theName: String, theAge: Int) {
   var name: String = theName   var age: Int = theAge
}

如果primary construct前面要聲明屬性,或者有annotation的話,關鍵字constructor不能省略:

class Person public @Inect constructor(var name: String)

Secondary constructor

如果primary constructor不能滿足需求怎么辦呢?還可以聲明其他constructor,所謂的secondary constructor:

class Person {
   var name: String constructor(name: String){
       this.name = name
   }
}

是不是看起來舒服一些,因為跟Java一樣了,可以把primary constfuctor和second constructor聯(lián)合起來一起用:

class Person(var name: String) {
    constructor(name: String, parrent: Person) : this(name) {
        parrent.addChild(this)
    }
}

這里要把secondary construct盡可能delegate到primary constructor,這里的delegate的意思就是primary constructor會在second constructor之前 執(zhí)行,還有就是initiailzer block都是在primary construct中執(zhí)行的,這就能保證initiliazer block在second constructor之前執(zhí)行。即使沒有顯示的聲明primary constructor,編譯器還是會生成一個默認的primary constructor以及把secondary constructor默認的delegate到primary constrcutor上面。也就是說,會保證primary constructor以及initializer block執(zhí)行在second constructor前面:

class Constructors {
    init {
        println("Initializer block")
    }

    constructor(i: Int) {
        println("second constructor")
    }
}

fun main(args: Array<String>) {
    val c = Constructors(3)
}

輸出:

Initializer block
second constructor

屬性和訪問方法

Kotlin會為聲明的屬性生成默認的setter和getter:

class Person(var name: Strring, var age: Int)

val p = Person("Kevin", 24)
p.getName() // 返回"Kevin"
p.setAge(32) // age變成了32

如果想自定義setter和getter,也是可以的:

class Person {
    var name: String
        set(n: String) {
            if (n == null || n == "") {
                name = "Unkown"
            } else {
                name = n
            }
        }
        get() {
            if (name == "Unkwon") {
                return "Nobody"
            }
            return name
        }
}

定義類的方法

跟聲明普通函數(shù)一樣,只不過是放在了類里面:

class Person(val name: String, val age: Int) {
    fun report() = "My name is $name, and I'm $age"
}

如果,要覆寫父類的方法,需要使用在方法聲明時加上override關鍵字。

class Doggy(val name: String) : Animal {
    override fun yell() = "Barking from $name"
}

訪問權限

訪問權限也跟Java類似分為public,protected,private以及internal,前三個意義也都一樣,只不過默認值不一樣,在Java里,如果對成員沒有指明,則是package scope,也就是同一個package可以訪問,但是Kotlin默認是public的。

internal是module內(nèi)部可見,有點類似于Java中的package,但是module定義跟package不一樣,module是一組編譯在一起的Kotlin文件,它跟編譯打包有關系,簡單的理解它的范圍要比package要大。

還有就是類,默認是不可被繼承的,相當于final class。如果想要允許繼承就要在聲明類的時候加上open。

字串

概念就不說了,大部分與Java一模一樣的,像支持的方法等。唯一需要說的就是字串模板,就是說把其他類型轉化為字串時,有較Java更為方便的方式:直接用$來把變量嵌入到字串之中,如:

val msg = "Error 1"
val count = 32
print("We got message $msg") //等同于"We got message " + msg
print("Total is $count") // Total is 32

lambda表達式

首先要介紹一個概念,高階函數(shù),其實就是把另外函數(shù)當作參數(shù)的函數(shù),或者說產(chǎn)生一個函數(shù),也即把函數(shù)作為返回值 的函數(shù)。前面說過,函數(shù)是一級對象,可以像常規(guī)變量一樣來使用,所以,就能把函數(shù)作為參數(shù)或者返回值來使用高階函數(shù)。lambda表達式就是為高階函數(shù)更方便使用而生的。

lambda 表達式

作為新時代的編程語言,都會支持函數(shù)式編程,而lambda表達 式又是函數(shù)式編程里面必不可少的一份子。其實啥是lambda表達式呢?說的簡單點就是沒有名字的函數(shù),非常簡短的,通常都是一兩句話的沒有名字的函數(shù)。就是長這個樣子{A, B -> C},這里面A,B是參數(shù),C是表達式,如:

val sum = { x: Int, y: Int -> x + y }

其中,參數(shù)的類型是可以省略的,因為編譯器能從上下文中推測出來:
max(strings, { a, b -> a.length < b.length }
表達式部分,可以不止一個,最后一個表達式作為返回值。

當把一個lambda表達作為最后一參數(shù),傳給某個函數(shù)時,可以直接把lambda表達式寫在參數(shù)的外面,比如:

val product = items.fold(1) { acc, e -> acc * e }

而當lambda是唯一的參數(shù)時,也可以把參數(shù)的括號省略掉:

run { println("Hello, world") }

還有就是,如果lambda表達中只有一個參數(shù),那么參數(shù)也可以省略,直接寫表達式:

eval{ x * x }

函數(shù)類型

前面提到了函數(shù)是可以像普通變量一樣使用的一級類,也就是說它是一個類型。它的具體形式是: (A, B)->C,其中括號內(nèi)的是參數(shù),C是返回類型,如:

val sum: (Int, Int)->Int = { x, y -> x + y }
val square: (Int)->Int = { x -> x * x }

為啥要提一下函數(shù)類型呢,因為有時需要聲明高階函數(shù):

fun walk(f: (Int)->Int)
fun run(f: ()->Unit)

Unit是一個特殊的返回值,相當于void,意思就是此函數(shù)沒有返回值。

集合

其實大部分跟Java是一樣的。只不過有一些函數(shù)式的操作,要多注意使用,從而讓代碼更簡潔,如:

  • 遍歷
  • 過濾
  • 映射
  • 排序
  • 折疊
  • 分組
  • 歸類

這些操作,對于大家應該都不難理解,就不一一解釋了,來斷代碼就知道了:

fun collectionTests() {
    val list = listOf("Apple", "Google", "Microsoft", "Facebook", "Twitter", "Intel", "QualComm", "Tesla")
    // 遍歷,以進行某種操作
    list.forEach{ println(it) }
    //按條件進行過濾,返回條件為true的
    val short = list.filter { it.length < 6 }
    println(short) // [Apple, Intel, Tesla]
    // 把列表元素映射成為另外一種元素
    val lenList = list.map{ it.length }
    println("Length of each item $lenList") //Length of each item [5, 6, 9, 8, 7, 5, 8, 5]
    // 按某種條件進行排序
    val ordered = list.sortedBy { it.length }
    println("Sorted by length $ordered") // Sorted by length [Apple, Intel, Tesla, Google, Twitter, Facebook, QualComm, Microsoft]
    // 折疊,用累積的結果繼續(xù)遍歷
    val joint = list.fold("", {partial, item -> if (partial != "")  "$partial, $item" else item })
    println("Joint list with comma $joint") // Joint list with comma Apple, Google, Microsoft, Facebook, Twitter, Intel, QualComm, Tesla
    //分組,用某種條件 把列表分成兩組
    val (first, second) = list.partition { it.length < 6 }
    println("Length shorter than 6 $first") // Length shorter than 6 [Apple, Intel, Tesla]
    println("Longer than 6 $second") // Longer than 6 [Google, Microsoft, Facebook, Twitter, QualComm]
    // 歸類,按某種方法把元素歸類,之后變成了一個Map
    val bucket = list.groupBy { it.length }
    println("$bucket is a map now") //{5=[Apple, Intel, Tesla], 6=[Google], 9=[Microsoft], 8=[Facebook, QualComm], 7=[Twitter]} is a map now
}

null處理

為了有效的減少空指針異常,Kotlin加入了Nullable類型,核心的原理是這樣的:聲明類型的時候要明確的告訴編譯器,這個變量是否可能為null,如果可能為null,那么可以賦null給這個變量,并且在使用此變量時必須檢查是否為null;假如這個變量不可能為null,那么是不可以賦null給此變量的。也就是說,編譯器會幫忙做一些檢查,以減少NullPointerException的發(fā)生。

Nullable變量

默認的變量聲明都是不可為null的,如:

var safe: String
safe = null // 會有compile error

要想允許變量為null,要在類型后面加一個問號,以告訴編譯器這是一個nullable類型:

var danger: String?
danger = null // OKay

使用時,nullable不能直接使用,必須檢查是否為null:

safe.length // okay
danger.length // compile error, danger could be null

檢查Nullable的真?zhèn)?/h4>

可以用傳統(tǒng)方式:

val len = if (danger != null) danger.length else -1

Safe call

既然有Nullable類型,自然就有配套的方式來更方便的使用它:

val len = danger?.length

如果danger是null就返回null,否則返回長度,注意它的返回值是一個Int?(又是一個Nullable類型)。這個還能鏈起來:

bob?.department?.head?.name

如果任何一環(huán)為null,則直接返回null。是不是感覺省了好多if (a == null)判斷。

Elvis operator

假如不能接受safe call返回的null,咋辦呢?想提供默認值的呢?也有方式:

val len = danger?.length
println(len ?: -1)

稍有點繞哈,首先,danger?.length返回一個Int?吧,那么?:的作用就是如果len是null,那么就返回-1,否則返回它的值。

強制取值符!!

它的作用是如果Nullable變量為null就拋出NullPointerException,如果正常的話就取其值,返回的類型是一個non-null類型:

val len = danger!!.length // get length or NullPointerException

盡管,編譯器可以幫助我們做一些事情,但是現(xiàn)實的項目中的大量的NPE并不是直接來源于,可以方便追蹤的賦值為null,而多是發(fā)生在多線程環(huán)境中,以及非常復雜的邏輯之中,編譯器能否追蹤到并警示,還有待考察。另外,就是雖有利器,但是要運用恰當,何時用允許null,何時不允許,還是要靠工程師的設計能力,比如盡可能返回空列表,空Map,或者空字串,而不是直接簡單的返回null,這就能減少一定的NPE。

Exercises

光是看書或者看教程是比較乏味的,學習編程最重要的是要上手去練習,這樣能加深印象,更好的理解書中或者教程中所講的概念和知識點。官方也準備了一個非常好的練習項目叫Kotlin-koans,非常適配初學習者來練手。
下面說一下如何使用這個練習項目:

  1. 官網(wǎng)去下載后,解壓
  2. 用Android Studio打開此項目,一切提示都回答yes
  3. 要想運行測試前需要先編譯一下項目,否則會提示找不到基礎的測試類,找到Gradle窗口,一般在右側,點開找到kotlin-koans->Tasks->build->build,運行它
  4. 現(xiàn)在就可以用先進的TDD方式來學習Kotlin了,在Project視圖下面,可以看到kotlin-koans項目,里面有兩個,一個是java,一個是tests,這兩個目錄里面的子目錄都是一一對應的,先運行tests下面的,會失敗,然后編輯java/下面的對應的代碼,直到測試通過。

Essence of Kotlin

致此,我們可以看出Kotlin這門語言的設計的核心理念:簡潔,這是Kotlin的核心理念,所以我們看到,一些機械的,重復的,可以從上下文中推測 出來的都 可以省略,以增加可讀性。我們在使用Kotlin的時候要踐行此理念,把語言的特性發(fā)揮到最大。
當然,簡潔,不是犧牲可讀性的方式來縮短代碼,而是要使用語言中的標準的簡潔的表達方式,比如lambda表達式,省略參數(shù)等。

要注意參考Kotlin conventions以及Android Kotlin conventions以寫出更加簡潔和容易理解的代碼。

Android dev setup

我們來新建一個項目,用純Kotlin實現(xiàn)一個Hello, world Android應用,來展示一下如何在Android中使用Kotlin:

注意: 這里使用的是Android Studio 3.1.2版本,默認就支持Kotlin,如果使用小于3.0的版本需要安裝Kotlin插件,可自行Google,孤還是建議先升級AS吧。

  1. 新建一個項目,其實流程跟新建一個普通Android Studio項目是一樣一樣的,從Android Studio3.0起,新建項目時就會有一個Checkbox,問你要不要添加Kotlin。這里把它選上。


    Step 1
  2. 就直接下一步就好


    Step 2
  3. Next,創(chuàng)建一個empty activity


    Step 3
  4. Finish

    Step 4
  5. 布局跟其他新建的Android項目無差別


    Step 5
  6. 代碼已經(jīng)是Kotlin的了


    Step 6
  7. 直接顯示"Hello, world"略顯無聊,所以加一下點擊事件:
class HelloActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_hello)

        val colorTable = listOf("#ff0000", "#00ff00", "#0000ff", "#ffff00", "#00ffff", "#ff00ff")
        val label = findViewById<TextView>(R.id.label)
        label.setOnClickListener { view ->
            val randomIndex = (Math.random() * colorTable.size).toInt()
            view.setBackgroundColor(Color.parseColor(colorTable[randomIndex]))
        }
    }
}

其實,整體來看,布局和項目的結構還是按照Android的方式來,唯一的不同是代碼可以用Kotlin來寫了。

Good to go

至此,Kotlin就算入門了,可以使用Kotlin來構建應用程序了,或者在你的項目中應用Kotlin了。

參考資料和有用的資料分享

原文鏈接:http://toughcoder.net/blog/2018/05/17/introduction-to-kotlin-programming-language/

?著作權歸作者所有,轉載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,119評論 6 531
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 98,382評論 3 415
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,038評論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,853評論 1 309
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,616評論 6 408
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 55,112評論 1 323
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,192評論 3 441
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,355評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 48,869評論 1 334
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 40,727評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,928評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,467評論 5 358
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 44,165評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,570評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,813評論 1 282
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,585評論 3 390
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,892評論 2 372

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

  • 前言 人生苦多,快來 Kotlin ,快速學習Kotlin! 什么是Kotlin? Kotlin 是種靜態(tài)類型編程...
    任半生囂狂閱讀 26,243評論 9 118
  • Android 自定義View的各種姿勢1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 172,618評論 25 708
  • 1.今天一天從上午出門一直忙到晚上8點鐘,一直在工作中度過以至于都沒時間吃飯,和我們一起的還有兩位朋友,在過程中我...
    Ai馬爺閱讀 169評論 0 0
  • 1.model B為model A的子字段2.model B的formview中, 系統(tǒng)自動生成 parent字段...
    yiangdea閱讀 1,195評論 0 0
  • 呼呼
    用心聽F閱讀 152評論 0 0