Kotlin語(yǔ)法進(jìn)階

一、類

image

1.1 類聲明

Kotin中使用關(guān)鍵字class聲明類,且默認(rèn)是public靴迫。如果一個(gè)類沒有類體濒蒋,可以省略花括號(hào)

class Invoice { /*……*/ }
class Test

關(guān)于類盐碱,有幾個(gè)細(xì)節(jié)需要注意:

  1. Kotlin中的成員變量必須初始化
  2. 類默認(rèn)不可以被繼承,如需被繼承沪伙,需要加上open關(guān)鍵字
  3. Kotlin中用:表示繼承與實(shí)現(xiàn)瓮顽,即同時(shí)表示Java中的extendsimplement
  4. Kotlin類中override變成關(guān)鍵字,且省略了protected關(guān)鍵字围橡,即override函數(shù)可見性繼承自父類

1.2 構(gòu)造函數(shù)

//Java 格式
public class User {
    int id;
    public User(int id, String name) {
        this.id = id;
    }
}
//kotlin格式
class User {
    val id: Int
    constructor(id: Int) {
        this.id = id
    }
}

Koltin中定義一個(gè)構(gòu)造函數(shù)暖混,使用constructor關(guān)鍵字,默認(rèn)為public翁授。一般不會(huì)像前述方式實(shí)現(xiàn)拣播,而是通過主構(gòu)造器實(shí)現(xiàn)。

主構(gòu)造函數(shù)

一個(gè)類可以有一個(gè)主構(gòu)造函數(shù)以及一個(gè)或多個(gè)次構(gòu)造函數(shù)收擦。

主構(gòu)造函數(shù)是類頭的一部分:它跟在類名(與可選的類型參數(shù))后贮配。

class Person constructor(firstName: String) { /*……*/ }
//如果主構(gòu)造函數(shù)沒有任何注解或者可見性修飾符,可以省略這個(gè) constructor 關(guān)鍵字炬守,等價(jià)于
class Person(firstName: String) { /*……*/ }

//如果構(gòu)造函數(shù)有注解或可見性修飾符牧嫉,這個(gè) constructor 關(guān)鍵字是必需的,并且這些修飾符在它前面
class Customer public @Inject constructor(name: String) { /*……*/ }

關(guān)于主構(gòu)造函數(shù),主要有兩部分內(nèi)容需要關(guān)注酣藻,即初始化代碼塊與屬性聲明

  • 初始化代碼塊

    主構(gòu)造函數(shù)不能包含任何代碼曹洽,所以初始化的代碼放到init 關(guān)鍵字作為前綴的初始化塊(initializer blocks)

    class Test constructor(num : Int){
        init {
            println("num = $num")
        }
    }
    
    var test = Test(1)    //類實(shí)例化
    

    init代碼塊執(zhí)行順序:在次構(gòu)造函數(shù)之前,主構(gòu)造函數(shù)之后執(zhí)行

    init代碼塊可以寫多個(gè)

  • 屬性聲明

    可以直接在主構(gòu)造函數(shù)中簡(jiǎn)便聲明屬性

    class Test(val num1 : Int, var num2 : Long, val str : String){
        ...
    }
    //等價(jià)于
    class Test(num1 : Int, num2 : Long, str : String){
        val num1: Int = num1
        var num2: Long = num2
        val str: String = str
        ...
    }
    

次構(gòu)造函數(shù)

Kotlin中支持二級(jí)構(gòu)造函數(shù)辽剧。以constructor關(guān)鍵字作為前綴

class Person {
    var children: MutableList<Person> = mutableListOf<Person>();
  constructor(parent: Person) {
        parent.children.add(this)
  }
}

如果類有一個(gè)主構(gòu)造函數(shù)送淆,每個(gè)次構(gòu)造函數(shù)需要委托給主構(gòu)造函數(shù),可以直接委托或者通過別的次構(gòu)造函數(shù)間接委托怕轿。
委托到同一個(gè)類的另一個(gè)構(gòu)造函數(shù)用 this 關(guān)鍵字

class User constructor(var name: String) {
    constructor(name: String, id: Int) : this(name) {
    }
    constructor(name: String, id: Int, age: Int) : this(name, id) {
    }
}

如果類主構(gòu)造函數(shù)的所有參數(shù)都具有默認(rèn)值偷崩,編譯器將生成一個(gè)額外的無參數(shù)構(gòu)造函數(shù),它將使用默認(rèn)值撞羽。

fun main(args: Array<String>) {
    var test = Test()         //num1 = 10  num2 = 20
    var test1 = Test(1,2)     //num1 = 1   num2 = 2
    var test2 = Test(4,5,6)       //num1 = 4   num2 = 5   num1 = 4     num2 = 5    num3 = 6
}

class Test constructor(num1: Int = 10 , num2: Int = 20){
    init {
        println("num1 = $num1\t num2 = $num2")
    }

    constructor(num1 : Int = 1, num2 : Int = 2, num3 : Int = 3) : this(num1 , num2){
        println("num1 = $num1\t num2 = $num2 \t num3 = $num3")
    }
}

類實(shí)例化

創(chuàng)建一個(gè)類的實(shí)例阐斜,調(diào)用類的構(gòu)造函數(shù)即可

val invoice = Invoice()

val customer = Customer("Joe Smith")

注意 Kotlin 并沒有 new 關(guān)鍵字

二、屬性與字段

2.1 屬性聲明

類中屬性用關(guān)鍵字varval進(jìn)行聲明

class Address {
    var name: String = "Holmes, Sherlock"
    var street: String = "Baker"
    var city: String = "London"
    var state: String? = null
}

屬性使用:直接用名稱引用即可

 fun copyAddress(address: Address): Address {
      val result = Address() // Kotlin 中沒有“new”關(guān)鍵字
      result.name = address.name // 將調(diào)用訪問器
      result.street = address.street
      // ……
      return result
  }

2.2 屬性訪問器

在Java中诀紊,需要加上getter方法和setter方法才可以調(diào)用屬性谒出,而在Kotlin中可直接用名稱引用,這時(shí)因?yàn)槠潆[含默認(rèn)實(shí)現(xiàn)了gettersetter(讀訪問器與寫訪問器)

class User {
    var name = "Czh"
    var userName: String
        get() = name 
        set(value) {
            name = value
        }

    //用val只讀標(biāo)識(shí)只讀
    val sex: String
        get() = "男"
}

這里有幾個(gè)細(xì)節(jié)需要重點(diǎn)說明一下:

  1. val修飾的只讀屬性邻奠,不能有寫訪問器setter()
  2. 關(guān)鍵字getset分別對(duì)應(yīng)gettersetter
  3. gettersetter自定義實(shí)現(xiàn)位于聲明的變量下方
  4. setter函數(shù)的參數(shù)為value笤喳,且使用時(shí)需要設(shè)置幕后字段

簡(jiǎn)單來說,Kotlin中每個(gè)成員變量就是一個(gè)屬性碌宴,每個(gè)屬性中包含了(Field + Getter + Setter)

屬性訪問器可見性

可以根據(jù)實(shí)際情況修改屬性訪問器的可見性

var str1 = "kotlin_1"
      private set       //setter()訪問器的私有化,且擁有默認(rèn)實(shí)現(xiàn)

var str3 = "kotlin_3"
      private get       //編譯出錯(cuò)杀狡,因?yàn)椴荒苡術(shù)etter()的訪問器可見性

屬性引用

屬性與方法一樣,可以獲取其引用贰镣。分為類獲取屬性引用和實(shí)例獲取屬性引用

  • 類獲取屬性引用

    // 通過類對(duì)象獲取該類的屬性引用
    val ageRef = Person::age
    // 通過類名屬性引用操作該屬性時(shí)需要一個(gè)Receiver
    val person = Person(18, "Kotlin")
    // 通過屬性引用調(diào)用get方法
    val age = ageRef.get(person)
    
  • 實(shí)例獲取屬性引用

    // 通過實(shí)例獲取該類的屬性引用
    val ageRef = person::age
    // 使用時(shí)無需Receiver
    ageRef.set(30)
    

其中獲取類中屬性引用時(shí)呜象,其屬性必須對(duì)獲取方式可見。

2.3 幕后字段

Kotlin 的類不能直接聲明域變量八孝,如果屬性需要董朝,kotlin會(huì)自動(dòng)提供鸠项。

在屬性的取值方法或設(shè)值方法中, 使用 field 標(biāo)識(shí)符可以引用這個(gè)backing field

var counter = 0 // 注意:這個(gè)初始器直接為后端域變量
    set(value) {
        if (value >= 0) field = value
    }

field 標(biāo)識(shí)符只能在屬性的訪問器內(nèi)使用

更好的理解backing field干跛,需要知道Kotlin中,訪問一個(gè)屬性的實(shí)質(zhì)是什么祟绊?

屬性的讀寫楼入,實(shí)質(zhì)是執(zhí)行了屬性的gettersetter訪問器

class Person {
    var name:String = "Paul"
      get() = "i am getter,name is Jake"
}

var person = Person()
val name = person.name
println("打印結(jié)果:$name")   //打印結(jié)果:i am getter,name is Jake
class Person {
    var name:String = "Paul"
        set(value) {
           println("執(zhí)行了寫訪問器,參數(shù)為:$value") 
        }
}

var person = Person()
person.name = "hi,this is new value"    //執(zhí)行了寫訪問器牧抽,參數(shù)為:hi,this is new value
println("打印結(jié)果:${person.name}")      //打印結(jié)果:Paul

注意:前述給屬性賦值嘉熊,執(zhí)行了寫訪問器setter,但是沒有屬性賦值扬舒,所以還是返回默認(rèn)值

如果按照J(rèn)ava的方法實(shí)現(xiàn)setter阐肤,則會(huì)直接報(bào)錯(cuò)

class Person {
var name = ""
  set(value) {
      this.name = value //報(bào)錯(cuò)
  }
}

上述代碼會(huì)直接內(nèi)存溢出錯(cuò)誤,反編譯為Java代碼

/**java*/
public final class Person {
    @NotNull
    private String name = "Paul";

    @NotNull
    public final String getName() {
        return this.name;
    }

    public final void setName(@NotNull String value) {
        this.setName(value);    //setName遞歸調(diào)用
    }
}

若無field字段,在set方法中進(jìn)行屬性賦值會(huì)出現(xiàn)遞歸調(diào)用的情況孕惜。

Kotlin中愧薛,如果屬性至少一個(gè)訪問器使用默認(rèn)實(shí)現(xiàn),則會(huì)自動(dòng)提供幕后字段衫画,用關(guān)鍵字field表示毫炉,其只能在gettersetter內(nèi)訪問。

class Person {
    var name:String = ""
    get() = field 
    set(value) {
        field = value
    }
}
class Person {
    var name:String = ""
}

上述兩個(gè)屬性聲明是等價(jià)的削罩。

通過backing field可以幫助我們?cè)趯傩栽L問器做一系列操作

class Person(var gender:Gender){
    var name:String = ""
      set(value) {
            field = when(gender){
              Gender.MALE -> "Jake.$value"
                Gender.FEMALE -> "Rose.$value"
          }
        }
}

enum class Gender{
    MALE,
    FEMALE
}

2.4 幕后屬性

有時(shí)我們希望一個(gè)屬性:對(duì)外表現(xiàn)為只讀瞄勾,對(duì)內(nèi)表現(xiàn)為可讀可寫,我們將這種屬性稱為幕后屬性

private var _table: Map<String, Int>? = null
public val table: Map<String, Int>
    get() {
        if (_table == null) {
            _table = HashMap() // 初始化
        }
        return _table ?: throw AssertionError("Set to null by another thread")
    }

_table屬性聲明為private,因此外部是不能訪問的弥激,內(nèi)部可以訪問进陡,外部訪問通過table屬性,而table屬性的值取決于_table微服,這里_table就是幕后屬性四濒。

三、可見性修飾符

  1. public:表示公有职辨,系統(tǒng)默認(rèn)使用此修飾符

  2. internal:表示模塊 盗蟆,例如創(chuàng)建一個(gè)函數(shù)僅開放給module內(nèi)部使用,不想對(duì)library的使用者可見舒裤,這是就應(yīng)該用internal可見性修飾符

  3. protected:表示私有+子類喳资。此修飾符不能用于頂層聲明。

  4. private:表示私有 腾供。

    Java中private表示類中可見仆邓,作為內(nèi)部類時(shí)對(duì)外部類可見。

    Kotlin中private表示類中或所在文件內(nèi)可見伴鳖,作為內(nèi)部類時(shí)對(duì)外部類不可見

    class Sample {
        private val propertyInClass = 1 // 僅 Sample 類中可見
    }
    
    private val propertyInFile = "A string." //整個(gè)文件可見
    
    class Outter {
        fun method() {
            val inner = Inner()
            ??
            val result = inner.number * 2 // 報(bào)錯(cuò)节值,對(duì)外部類不可見
        }
        class Inner {
            private val number = 1
        }
    }
    

四、繼承類

4.1 定義

定義繼承類的關(guān)鍵字為:open榜聂。其中類搞疗、成員,方法都要使用open關(guān)鍵字须肆,不過對(duì)于abstract類默認(rèn)具有open屬性匿乃。

open class Demo{

    open var num = 3
    open fun foo() = "foo"
    open fun bar() = "bar"
}

class DemoTest : Demo(){
    // Kotlin使用繼承是使用`:`符號(hào)
}

其中在 Kotlin 中,所有類都有一個(gè)共同的超類 Any豌汇,這對(duì)于沒有超類型聲明的類是默認(rèn)超類

class Example // 從 Any 隱式繼承

Any 有三個(gè)方法:equals()幢炸、 hashCode()toString()

修飾符 相應(yīng)類的成員 注解
final 不能被覆寫 在kotlin中默認(rèn)所有的方法和類都是final屬性
open 可以被覆寫 需要被明確指出
abstract 必須要覆寫 不能被實(shí)例化拒贱,默認(rèn)具有open屬性宛徊。
override 覆寫超類的方法 如果沒有被指定為final佛嬉,則默認(rèn)具有open屬性

4.2 構(gòu)造函數(shù)

主要分為實(shí)現(xiàn)類無主構(gòu)造函數(shù)和有主構(gòu)造函數(shù)。

實(shí)現(xiàn)類無主構(gòu)造函數(shù)

這種情況闸天,次構(gòu)造函數(shù)必須使用super關(guān)鍵字初始化基類型或委托給另一個(gè)構(gòu)造函數(shù)

class MyView : View{

    constructor(context: Context) : super(context)

    constructor(context: Context, attrs: AttributeSet?) : super(context, attrs)

    constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int)
        : super(context, attrs, defStyleAttr)
}

存在主構(gòu)造函數(shù)

這種情況巷燥,其基類必須用實(shí)現(xiàn)類主構(gòu)造函數(shù)的參數(shù)就地初始化

class MyView(context: Context?, attrs: AttributeSet?, defStyleAttr: Int)
    : View(context, attrs, defStyleAttr) {

    constructor(context: Context?) : this(context,null,0)
    
    constructor(context: Context?,attrs: AttributeSet?) : this(context,attrs,0)
}

4.3 重寫與重載

重寫

當(dāng)基類中的函數(shù),沒有用open修飾符修飾的時(shí)候号枕,實(shí)現(xiàn)類中不得出現(xiàn)與基類相同函數(shù)名的函數(shù)

open class Demo{
    fun test(){}
}

class DemoTest : Demo(){
    fun test(){}   //編輯器直接報(bào)紅報(bào)錯(cuò)
    override fun test(){}   //同樣報(bào)錯(cuò)
}

當(dāng)一個(gè)基類去繼承另外一個(gè)基類時(shí)缰揪,第二個(gè)基類不想去覆蓋掉第一個(gè)基類的方法時(shí),第二個(gè)基類的該方法使用final修飾符修飾葱淳。

open class A{
    open fun foo(){}
}

open class B : A(){
    // 這里使用final修飾符修飾該方法钝腺,禁止覆蓋掉類A的foo()函數(shù)
    final override fun foo(){}
}

重載

Kotlin中函數(shù)重載與Java一致

open class Demo{
    open fun foo() = "foo"
}

class DemoTest : Demo(){

    fun foo(str: String) : String{
        return str
    }

    override fun foo(): String {
        return super.foo()
    }
}    

屬性重寫

在基類中聲明的屬性,可在其實(shí)現(xiàn)類中重寫該屬性赞厕。

  1. 該屬性必須以override關(guān)鍵字修飾
  2. 重寫前后屬性類型保持一致
  3. 僅可重寫屬性的Getter
  4. 可以用var重寫基類的val修飾的屬性艳狐,反之不行
open class Demo{
    open var num = 3
}

class DemoTest : Demo(){
    override var num: Int = 10
}
open class Demo{
    open val valStr = "我是用val修飾的屬性"
}

class DemoTest : Demo(){
    override var valStr: String = "abc"
        set(value){field = value}
}

//重寫屬性的時(shí)候不用get() = super.xxx,因?yàn)檫@樣的話,不管你是否重新為該屬性賦了新值皿桑,還是支持setter(),在使用的時(shí)候都調(diào)用的是基類中的屬性值毫目。
class DemoTest : Demo(){

    override var valStr: String = "abc"、
        get() = super.valStr
        set(value){field = value}
}

fun main(arge: Array<String>>){
    println(DemoTest().valStr)  //我是用val修飾的屬性

    val demo = DemoTest()
    demo.valStr = "1212121212"
    println(demo.valStr)    //我是用val修飾的屬性
}

重寫屬性的時(shí)候不用get() = super.xxx,因?yàn)檫@樣的話诲侮,不管你是否重新為該屬性賦了新值镀虐,還是支持setter(),在使用的時(shí)候都調(diào)用的是基類中的屬性值。

open class Demo{
    open val valStr = "我是用val修飾的屬性"
}

class DemoTest : Demo(){
    override var valStr: String = "abc"沟绪、
        get() = super.valStr
        set(value){field = value}
}

println(DemoTest().valStr)  //我是用val修飾的屬性
val demo = DemoTest()
demo.valStr = "1212121212"
println(demo.valStr)    //我是用val修飾的屬性

4.4 初始化順序

open class Base(val name: String) {

    //基類初始化塊
    init { println("Initializing Base") }
    //基類屬性初始化
    open val size: Int = 
    name.length.also { println("Initializing size in Base: $it") }
}
class Derived(
    name: String,
    val lastName: String
) : Base(name.capitalize().also { println("Argument for Base: $it") }) {

    //子類初始化塊
    init { println("Initializing Derived") }

    //子類屬性初始化
    override val size: Int =
    (super.size + lastName.length).also { println("Initializing size in Derived: $it") }
}

fun main() {
    val d = Derived("hello", "world")
}
Argument for Base: Hello        // 基類init代碼塊
Initializing Base
Initializing size in Base: 5    //基類屬性初始化
Initializing Derived            //子類init代碼塊
Initializing size in Derived: 10    //子類屬性初始化

可知繼承類的執(zhí)行順序是:先基類的init代碼塊刮便,然后是基類的屬性初始化,接下來是子類的init代碼塊與屬性的初始化绽慈。

五恨旱、接口與枚舉

image

5.1 接口定義

使用關(guān)鍵字 interface 來定義接口,且一個(gè)類或?qū)ο罂梢詫?shí)現(xiàn)一個(gè)或多個(gè)接口

interface MyInterface {
    fun bar()
    fun foo() {
      // 可選的方法體
    }
}

class Child : MyInterface {
    override fun bar() {
    // 方法體
    }
}

5.2 屬性與方法

接口屬性

接口屬性主要有如下特點(diǎn):

  1. 屬性要么是抽象坝疼,要么提供訪問器的實(shí)現(xiàn)
  2. 不可以有幕后字段
interface Demo3Interface{
    val num1: Int   //抽象的
    val num2 : Int  
}

class Demo3(override val num1: Int, override val num2: Int) : Demo3Interface{
    fun sum() : Int{
        return num1 + num2
    }
}

var demo = Demo3(1,2)
println(demo.sum()) // 3
interface Demo3Interface{

    // val num3: Int = 3  這種方式不提供搜贤,會(huì)直接報(bào)錯(cuò)的
    val num3: Int
    get() = 3

    val num4: Int
}

class Demo3(override val num1: Int, override val num2: Int) : Demo3Interface{

    // 提供訪問器實(shí)現(xiàn)
    override val num3: Int
    get() = super.num3

    // 手動(dòng)賦值
    override var num4: Int = 4

    fun result() : Int{
        return num3 + num4
    }
}

fun main(args: Array<String>) {
    println(demo.result())      // 7

    // 在這里也可以改變接口屬性的值
    demo.num4 = 10
    println(demo.result())  // 13
}

接口方法

接口方法主要有如下特點(diǎn):

  1. 不帶結(jié)構(gòu)體的函數(shù)可以省略大括號(hào)
  2. 不用強(qiáng)制重寫帶結(jié)構(gòu)體的函數(shù),可直接調(diào)用
interface Demo2Interface{

    fun fun1()  //無參無返回
    fun fun2(num: Int)  //有參無返回
    fun fun3(num: Int) : Int    //有參有返回

    // 有結(jié)構(gòu)體钝凶, 可以不重寫
    fun fun4() : String{
        return "fun4"
    }

    fun fun5(){}    //等價(jià)fun1
}

class Demo2 : Demo2Interface{

    override fun fun1() {
        println("我是fun1()方法")
    }

    override fun fun2(num: Int) {
        println("我是fun2()方法仪芒,我的參數(shù)是$num")
    }

    override fun fun3(num: Int): Int {
        println("我是fun3()方法,我的參數(shù)是$num腿椎,并且返回一個(gè)Int類型的值")
        return num + 3
    }

    override fun fun4(): String {
        println("我是fun4()方法桌硫,并且返回一個(gè)String類型的值")
        return super.fun4()
    }
}
fun main(args: Array<String>) {
    var demo = Demo2()

    demo.fun1()
    demo.fun2(5)
    println(demo.fun3(10))
    println(demo.fun4())

    //可以不重寫該方法直接調(diào)用
    demo.fun5()
}
/************輸出結(jié)果***************/
我是fun1()方法
我是fun2()方法夭咬,我的參數(shù)是5
我是fun3()方法啃炸,我的參數(shù)是10,并且返回一個(gè)Int類型的值
13
我是fun4()方法卓舵,并且返回一個(gè)String類型的值
fun4

5.3 沖突解決

某些場(chǎng)景下南用,可能出現(xiàn)不同父類存在相同方法名的方法,導(dǎo)致子類實(shí)現(xiàn)時(shí)沖突,用用super<接口名>.方法名來區(qū)分

interface A {
    fun foo() { print("A") }
    fun bar()
}

interface B {
    fun foo() { print("B") }
    fun bar() { print("bar") }
}
class C : A {
    override fun bar() { print("bar") }
}

class D : A, B {
    override fun foo() {
        super<A>.foo()
        super<B>.foo()
    }

    override fun bar() {
        super<B>.bar()
    }
}

5.4 枚舉定義

聲明格式

enum class 類名{
      ...
}

enum class State{
    NORMAL,NO_DATA,NO_INTERNET,ERROR,OTHER
}

//訪問枚舉常量
State.NORMAL.name
State.NO_DATA.name

Kotlin中枚舉的特點(diǎn)是:

  1. 每一個(gè)枚舉常量都是一個(gè)對(duì)象裹虫,用逗號(hào)分隔
  2. 訪問方式為:枚舉類名.枚舉常量.屬性

枚舉匿名類

實(shí)現(xiàn)枚舉常量的匿名類肿嘲,須提供一個(gè)定義在枚舉類內(nèi)部的抽象方法,且必須在枚舉變量后面筑公,最后一個(gè)枚舉變量用分號(hào)結(jié)束雳窟。

enum class ConsoleColor {
    BLACK {
        override fun print() {
            println("我是枚舉常量 BLACK ")
        }
    },
    GREEN {
        override fun print() {
            println("我是枚舉常量 GREEN ")
        }
    };

    abstract fun print()
}

fun main(args: Array<String>) {
    ConsoleColor.BLACK.print()  //我是枚舉常量 BLACK 
}

5.5 枚舉類使用

枚舉類對(duì)象內(nèi)置兩個(gè)屬性:

  • name:枚舉對(duì)象名稱
  • ordinal:枚舉對(duì)象索引

枚舉類同時(shí)提供了values()valueOf()方法來檢測(cè)指定的名稱與枚舉類中定義的任何枚舉常量是否匹配,且提供enumValues()enumValueOf()函數(shù)以泛型的方式訪問枚舉類中的常量

enum class Color(var argb : Int){
    RED(0xFF0000),
    WHITE(0xFFFFFF),
    BLACK(0x000000),
    GREEN(0x00FF00)
}  
println("name = " + Color.RED.name + "\tordinal = " + Color.RED.ordinal)
println("name = " + Color.WHITE.name + "\tordinal = " + Color.WHITE.ordinal)
println("name = " + Color.BLACK.name + "\tordinal = " + Color.BLACK.ordinal)
println("name = " + Color.GREEN.name + "\tordinal = " + Color.GREEN.ordinal)

/************輸出結(jié)果*********/
name = RED      ordinal = 0
name = WHITE    ordinal = 1
name = BLACK    ordinal = 2
name = GREEN    ordinal = 3
println(enumValues<Color>().joinToString { it.name })
println(enumValueOf<Color>("RED"))

/************輸出結(jié)果*********/
RED, WHITE, BLACK, GREEN
RED
println(Color.valueOf("RED"))
println(Color.values()[0])
println(Color.values()[1])
println(Color.values()[2])
println(Color.values()[3])

 /************輸出結(jié)果*********/
RED
RED
WHITE
BLACK
GREEN

枚舉的一些其他特性

  • 可以定義擴(kuò)展方法和擴(kuò)展屬性
  • 可以用在條件分支when
  • 有順序匣屡,可以用來比較大小
  • 可以應(yīng)用在區(qū)間
  • 也是類封救,可以實(shí)現(xiàn)接口

六、數(shù)據(jù)類

Kotlin中提供了關(guān)鍵字data捣作,聲明一個(gè)類為數(shù)據(jù)類

data class 類名(var param1 :數(shù)據(jù)類型,...){}
//或者
data class 類名 可見性修飾符 constructor(var param1 : 數(shù)據(jù)類型 = 默認(rèn)值,...)

數(shù)據(jù)類的主要特點(diǎn)是:

  1. 沒有結(jié)構(gòu)體的時(shí)候誉结,大括號(hào){}可省略
  2. 主構(gòu)造函數(shù)至少存在一個(gè)參數(shù),用varval修飾
  3. 不能是抽象的券躁,開放的或者內(nèi)部的
  4. 可以實(shí)現(xiàn)接口或繼承其他類
//kotlin
data class User(val name : String, val pwd : String)

上述代碼等價(jià)于

//Java
public class User {
    private String name;
    private String pwd;

    public User(String name, String pwd) {
        this.name = name;
        this.pwd = pwd;
    }
    // ......
    
    @Override
    public String toString() {
        return "User{" +
                "name='" + name + '\'' +
                ", pwd='" + pwd + '\'' +
                '}';
    }
}
    

copy

如需復(fù)制一個(gè)數(shù)據(jù)對(duì)象并改變它的一些屬性惩坑,其余保持不變,可用其copy函數(shù)也拜。

val mUser = User("kotlin","123456")
println(mUser)  // User(name=kotlin, pwd=123456)
val mNewUser = mUser.copy(name = "new Kotlin")
println(mNewUser)   // User(name=new Kotlin, pwd=123456)

解構(gòu)聲明

解構(gòu)聲明即把一個(gè)對(duì)象 解構(gòu)成很多變量以舒。

Kotlin中定義一個(gè)數(shù)據(jù)類,則系統(tǒng)會(huì)默認(rèn)自動(dòng)根據(jù)參數(shù)的個(gè)數(shù)生成component1() ... componentN()函數(shù)慢哈。其...,componentN()函數(shù)就是用于解構(gòu)聲明

val mUser = User("kotlin","123456")
val (name,pwd) = mUser
println("name = $name\tpwd = $pwd")

備注:

關(guān)于data數(shù)據(jù)類序列化問題稀轨,參考資料:https://cloud.tencent.com/developer/article/1587304

七、密封類

密封類用來表示受限的類繼承結(jié)構(gòu):當(dāng)一個(gè)值為有限幾種的類型岸军、而不能有任何其他類型時(shí)奋刽。某種意義上講,它相當(dāng)于是枚舉類的擴(kuò)展

密封類的子類必須是在密封類的內(nèi)部或必須存在于密封類的同一文件

kotlin1.5放寬了限制艰赞,只需要保證 Sealed Classes 和它的子類佣谐,在同一個(gè)包名和 module 下面即可

聲明

定義密封類的關(guān)鍵字:sealed方妖,聲明格式為:

sealed class SealedExpr()

sealed class Expr {
    data class Const(val number: Double) : Expr()
    data class Sum(val e1: Expr, val e2: Expr) : Expr()
}
object NotANumber : Expr()  // 子類可以定在密封類外部

密封類不能被實(shí)例化狭魂,類似抽象類。通過反編譯可知党觅,內(nèi)部的子類編譯為靜態(tài)內(nèi)部類

使用

密封類主要的作用在于限制繼承雌澄,起到劃分子類的作用,也可保證安全杯瞻。一般與when合用镐牺。

sealed class SealedClass{
    class SonClass1: SealedClass(){}

    class SonClass2: SealedClass(){}
}
fun check(fatherClass: FatherClass): String =
when(fatherClass){
    is SonClass1 -> "1"
    is SonClass2 -> "2"
    else ->
    throw IllegalArgumentException("Unknown Class")
}

fun main(args: Array<String>){
    val son1 = SonClass1();
    val result = check(son1)
    println(result)
}

前述代碼,每在SealedClass密封類添加一個(gè)子類魁莉,when結(jié)構(gòu)就會(huì)檢查到增加了子類睬涧,必須給when結(jié)構(gòu)添加分支募胃,否則編譯報(bào)錯(cuò)

密封類一般也稱為枚舉類的擴(kuò)展sealed不能修飾interface畦浓、abstract class

和抽象類類似痹束,Sealed Class可用于表示層級(jí)關(guān)系。它的子類可以是任意的類:data class讶请、普通Kotlin對(duì)象祷嘶、普通的類,甚至也可以是另一個(gè)密封類

sealed class ResponseResult<out T> {
    data class Success<out T>(val data:T):ResponseResult<T>()

    data class Failure(val throwable: Throwable?):ResponseResult<Nothing>()

    override fun toString(): String {
        return when(this) {
            is Success<*> -> "Success=[data=$data]"
            is Failure -> "Failure[throwable=$throwable]"
        }
    }
}

inline fun <reified T> ResponseResult<T>.doSuccess(success:(T) -> Unit) {
    if (this is ResponseResult.Success) {
        success(data)
    }
}

inline fun <reified T> ResponseResult<T>.doFailure(failure:(Throwable?) -> Unit) {
    if (this is ResponseResult.Failure) {
        failure(throwable)
    }
}
fun main() {
    val result = if(true) {
        ResponseResult.Success(xxx)
    } else {
        ResponseResult.Failure(xxx)
    }
    
    result.doSuccess{
        
    }
    
    result.doError{
        
    }
}

八夺溢、其它類

8.1 抽象類

定義

用關(guān)鍵字abstract聲明抽象類

抽象類和普通類的區(qū)別在于:抽象類除了可以有其自己的屬性抹蚀、構(gòu)造函數(shù)、方法等組成部分企垦,還包含了抽象函數(shù)以及抽象屬性环壤。

abstract class Lanauage{
    val TAG = this.javaClass.simpleName  // 自身的屬性

    fun test() : Unit{

    }
    abstract var name : String           // 抽象屬性
    abstract fun init()                  // 抽象方法
}
class TestAbstarctA : Lanauage(){

    override var name: String
    get() = "Kotlin"
    set(value) {}

    override fun init() {
        println("我是$name")
    }
}

val mTestAbstarctA = TestAbstarctA()
println(mTestAbstarctA.name)    //Kotlin
mTestAbstarctA.init()   //我是Kotlin

抽象類的主要特點(diǎn)為:

  1. 不能被直接實(shí)例化
  2. 抽象成員只有定義,沒有實(shí)現(xiàn)
  3. 只能繼承一個(gè)抽象類
  4. 可以覆寫抽象類父類的函數(shù)

8.2 嵌套類

定義

類可以嵌套在其他類中钞诡,嵌套類默認(rèn)是靜態(tài)的郑现。

class Other{           // 外部類
    val numOuther = 1

    class Nested {      // 嵌套類
        fun init(){
            println("執(zhí)行了init方法")
        }
    }
}

Other.Nested().init()      // 調(diào)用格式為:外部類.嵌套類().嵌套類方法/屬性

嵌套類的主要特點(diǎn)為:

  1. 調(diào)用嵌套類屬性或方法格式:外部類.嵌套類().嵌套類方法/屬性
  2. 無法訪問外部類的屬性和成員

8.3 內(nèi)部類

聲明一個(gè)內(nèi)部類使用inner關(guān)鍵字。

內(nèi)部類會(huì)帶有一個(gè)對(duì)外部類的對(duì)象的引用荧降,能夠訪問外部類的成員接箫,但內(nèi)部類不能在接口或非內(nèi)部嵌套類中聲明。

class Other{            // 外部類
    val numOther = 1

    inner class InnerClass{     // 嵌套內(nèi)部類
        val name = "InnerClass"
        fun init(){
            println("我是內(nèi)部類")
        }
    }
}

Other().InnerClass().init()  // 調(diào)用格式為:外部類().內(nèi)部類().內(nèi)部類方法/屬性

8.4 匿名內(nèi)部類

通過object來創(chuàng)建匿名內(nèi)部類

interface OnClickListener{
    fun onItemClick(str : String)
}

class Other{
    
  private lateinit var listener : OnClickListener

    fun setOnClickListener(listener: OnClickListener){
        this.listener = listener
    }
    
    fun testListener(){
        listener.onItemClick("我是匿名內(nèi)部類的測(cè)試方法")
    }
}    
// 測(cè)試匿名內(nèi)部類
val other = Other()
other.setOnClickListener(object : OnClickListener{
    override fun onItemClick(str: String) {
        // todo
        println(str)
    }
})
other.testListener()

8.5 局部類

局部類即定義在方法中的類

    val numOther = 1

    fun partMethod(){
        var name : String = "partMethod"

        class Part{
            var numPart : Int = 2

            fun test(){
                name = "test"
                numPart = 5
                println("我是局部類中的方法")
            }
        }

        val part = Part()
        println("name = $name \t numPart = " + part.numPart + "\t numOther = numOther")
        part.test()
        println("name = $name \t numPart = " + part.numPart + "\t numOther = numOther")
    }
}
Other().partMethod()

/***********輸出結(jié)果***********/
name = partMethod    numPart = 2    numOther = 1
我是局部類中的方法
name = test      numPart = 5    numOther = 1

局部類的主要特點(diǎn)是:

  1. 局部類只能在定義該局部類的方法中使用朵诫。
  2. 定義在實(shí)例方法中的局部類可以訪問外部類的所有變量和方法辛友。但不能修改
  3. 局部類中的可以定義屬性、方法剪返,并且可以修改局部方法中的變量

九废累、類型別名

類型別名是為現(xiàn)有類型提供替代名稱,例如用較短名稱替代原有類型脱盲,解決代碼過于冗余與臃腫的問題邑滨,關(guān)鍵字為typelias

類名

// 類型別名,切記聲明在頂部
typealias First = TypeAliasDemoTestFirst

class TypeAliasDemoTestFirst{
    fun show(){
        println("name : ${this.javaClass.simpleName}")
    }
}

val first = First()
first.show()
typealias NestA = DemoClassTestNest.A

class DemoClassTestNest{
    class A{
        fun show(){
            println("name : ${this.javaClass.simpleName}")
        }
    }
}

val nestA = NestA()
nestA.show()

函數(shù)參數(shù)

typealias Predicate<T> = (T) -> Boolean

fun foo(p: Predicate<Int>) = p(42)

fun main() {
    val f: (Int) -> Boolean = { it > 0 }
    println(foo(f)) // 輸出 "true"

    val p: Predicate<Int> = { it > 0 }
    println(listOf(1, -2).filter(p)) // 輸出 "[1]"
}

屬性名沖突解決

在開發(fā)中钱反,處理json字符串轉(zhuǎn)換成實(shí)體類的時(shí)候掖看,會(huì)出現(xiàn)屬性名和關(guān)鍵字沖突的問題

對(duì)于Java,使用@SerializedName注解解決

public class Test{
    private int id;
    private String name;
    @SerializedName("swicth")
    private int s;
}

對(duì)于Kotlin面哥,只需將屬性名包括在符號(hào)``中即可

data class TestBean(val id : Int,
                val name : String,
              val `package` : String)

十哎壳、解構(gòu)聲明

解構(gòu)聲明:將一個(gè)對(duì)象解構(gòu)為多個(gè)變量的操作。data數(shù)據(jù)類默認(rèn)支持解構(gòu)聲明尚卫,會(huì)生成component()方法归榕。

data class Book(var name: String, var price: Float)

val (name, price) = Book("Kotlin入門", 66.6f) //解構(gòu)聲明
println(name)   //Kotlin入門
println(price)  //66.6

實(shí)現(xiàn)原理

數(shù)據(jù)類之所以可以使用解構(gòu)聲明,因?yàn)榫幾g器默認(rèn)為它聲明了類似函數(shù)

operator fun component1(): String {
    return name
}

operator fun component2(): Float {
    return price
}

其中的 component1()component2() 函數(shù)是在 Kotlin 中廣泛使用的 約定原則 的另一個(gè)例子焕毫,為 name蹲坷、price變量賦值相當(dāng)于分別調(diào)用了 Book 對(duì)象的component1()循签、component2()函數(shù)

image

自定義解構(gòu)聲明

對(duì)于普通類疙咸,可以手動(dòng)聲明類似operator fun component1()的方法县匠,來實(shí)現(xiàn)解構(gòu)聲明功能

class Book(var name: String, var price: Float) {
    operator fun component1(): String {
        return name
    }

    operator fun component2(): Float {
        return price
    }
}

fun main(args: Array<String>) {
    val (name, price) = Book("Kotlin入門", 66.6f)
    println(name)   //Kotlin入門
    println(price)  //66.6
}

數(shù)組撒轮、集合解構(gòu)聲明

Kotlin 中數(shù)組,list题山、map系列集合默認(rèn)也支持解構(gòu)聲明的功能

fun main(args: Array<String>) {
    val array = arrayOf(1, 2, 3)
    val (a1, a2, a3) = array
    
    val list = listOf(1, 2, 3)
    val (b1, b2, b3) = list
    
    val map = mapOf("key1" to 1, "key2" to 2, "key3" to 3)
    for ((key, value) in map) {
        println("$key-$value")
    }
}

解構(gòu)聲明忽略

如果在解構(gòu)聲明中不需要某個(gè)變量兰粉,那么可以用下劃線_取代其名稱玖姑,這樣也就不會(huì)調(diào)用相應(yīng)的componentN()操作符函數(shù):

val (_, price) = Book("Kotlin入門", 66.6f)

十一慨菱、對(duì)象表達(dá)式與對(duì)象聲明

11.1 對(duì)象表達(dá)式

對(duì)象表達(dá)式是在使用他們的地方立即執(zhí)行

創(chuàng)建一個(gè)繼承某個(gè)(或某些)類型的匿名類的對(duì)象幻林,我們一般會(huì)這么寫:

window.addMouseListener(object : MouseAdapter() {
    override fun mouseClicked(e: MouseEvent) { /*……*/ }

    override fun mouseEntered(e: MouseEvent) { /*……*/ }
})

kotlin和Java在創(chuàng)建匿名類的方式相似老速,只不過將new換成了object

定義一個(gè)接口與抽象類畏腕,kotlin可以一個(gè)匿名對(duì)象搞定郊尝,而java需要定義兩個(gè)匿名對(duì)象類

abstract class Person {
    abstract fun play()
}
interface Move {
    fun walk()
}
//對(duì)象表達(dá)式
val a: Any = object : Person(), Move {
    override fun walk() {
        println("walk")
    }
    override fun play() {
        println("play")
    }
}


fun main(args: Array<String>) {
    //調(diào)用
    if (a is Person) {
        a.play()
    }
    if (a is Move) {
        a.walk()
    }
}

越過類定義

通過對(duì)象表達(dá)式流昏,可以越過類的定義况凉,直接得到一個(gè)對(duì)象

fun foo() {
    val adHoc = object {
        var x: Int = 0
        var y: Int = 0
    }
    print(adHoc.x + adHoc.y)
}

返回值類型

匿名對(duì)象可以用作只在本地和私有作用域中聲明的類型各拷。

  1. 作為公有函數(shù)的返回類型或公有屬性類型烤黍,實(shí)際類型為匿名對(duì)象的超類型
  2. 如果是私有傻盟,則為匿名對(duì)象類型
class C {
    // 私有函數(shù)娘赴,所以其返回類型是匿名對(duì)象類型
    private fun foo() = object {
        val x: String = "x"
    }

    // 公有函數(shù)诽表,所以其返回類型是 Any
    fun publicFoo() = object {
        val x: String = "x"
    }

    fun bar() {
        val x1 = foo().x        // foo私有函數(shù)隅肥,沒問題腥放,
        val x2 = publicFoo().x  // 錯(cuò)誤:公用函數(shù)返回超類型秃症,未能解析的引用“x”
    }
}

對(duì)象表達(dá)式中的代碼可以訪問來自包含它的作用域的變量。

fun countClicks(window: JComponent) {
    var clickCount = 0
    var enterCount = 0

    window.addMouseListener(object : MouseAdapter() {
        override fun mouseClicked(e: MouseEvent) {
            clickCount++
        }

        override fun mouseEntered(e: MouseEvent) {
            enterCount++
        }
    })
    // ……
}

11.2 對(duì)象聲明

Kotlin 使用 object 關(guān)鍵字來聲明一個(gè)對(duì)象宗雇,其中變量與函數(shù)都是靜態(tài)的赔蒲。

可以通過對(duì)象聲明來獲得一個(gè)單例舞虱,且是線程安全的餓漢式單例母市。

object DataProviderManager {
    fun registerDataProvider(provider: DataProvider) {
        // ……
    }

    val allDataProviders: Collection<DataProvider>
        get() = // ……
}

對(duì)象聲明在另一個(gè)類的內(nèi)部時(shí)患久,只能通過類名來訪問蒋失,且該對(duì)象不能直接訪問到外部類的方法和變量

class Site {
    var name = "Hello World"
    object DeskTop{
        var url = "www.runoob.com"
        fun showName(){
            print{"desk legs $name"} // 錯(cuò)誤,不能訪問到外部類的方法和變量
        }
    }
}
fun main(args: Array<String>) {
    var site = Site()
    site.DeskTop.url // 錯(cuò)誤荆萤,不能通過外部類的實(shí)例訪問到該對(duì)象
    Site.DeskTop.url // 正確
}

伴生對(duì)象

類內(nèi)部的對(duì)象聲明可以用 companion關(guān)鍵字標(biāo)記链韭,可以直接通過外部類訪問到對(duì)象的內(nèi)部元素敞峭,類似于Java中的static靜態(tài)功能

class MyClass {
    companion object Factory {
        fun create(): MyClass = MyClass()
    }
}

val instance = MyClass.create()   // 訪問到對(duì)象的內(nèi)部元素

可以省略伴生對(duì)象的名稱儡陨,在這種情況下將使用名稱 Companion

class MyClass {
    companion object { }
}

val x = MyClass.Companion

一個(gè)類里只能聲明一個(gè)內(nèi)部關(guān)聯(lián)對(duì)象骗村,即companion只能使用一次

Java中的靜態(tài)變量和方法胚股,在kotlin中都放在companion object中裙秋。因此Java中的靜態(tài)初始化在kotlin中也放在companion object中摘刑,由init和一對(duì)大括號(hào)表示枷恕。

class Sample {
    companion object {
      init {
            ...
        }
    }
}

伴生對(duì)象是真實(shí)對(duì)象的實(shí)例成員,而且還可以實(shí)現(xiàn)接口

interface Factory<T> {
    fun create(): T
}

class MyClass {
      companion object : Factory<MyClass> {
          override fun create(): MyClass = MyClass()
      }
      fun test() {
          println("test")
      }
  }

  fun main(args: Array<String>) {
      val f: Factory<MyClass> = MyClass
      f.create().test()
  }

語(yǔ)義差異

  • 對(duì)象表達(dá)式是在使用他們的地方立即執(zhí)行(及初始化)的未玻;
  • 對(duì)象聲明是在第一次被訪問到時(shí)延遲初始化的扳剿;
  • 伴生對(duì)象的初始化是在相應(yīng)的類被加載(解析)時(shí)昼激,與 Java 靜態(tài)初始化器的語(yǔ)義相匹配
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末橙困,一起剝皮案震驚了整個(gè)濱河市纷宇,隨后出現(xiàn)的幾起案子像捶,更是在濱河造成了極大的恐慌桩砰,老刑警劉巖亚隅,帶你破解...
    沈念sama閱讀 216,544評(píng)論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件煮纵,死亡現(xiàn)場(chǎng)離奇詭異行疏,居然都是意外死亡酿联,警方通過查閱死者的電腦和手機(jī)贞让,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,430評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門喳张,熙熙樓的掌柜王于貴愁眉苦臉地迎上來美澳,“玉大人人柿,你說我怎么就攤上這事凫岖「绶牛” “怎么了?”我有些...
    開封第一講書人閱讀 162,764評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)社露。 經(jīng)常有香客問我,道長(zhǎng)脱拼,這世上最難降的妖魔是什么熄浓? 我笑而不...
    開封第一講書人閱讀 58,193評(píng)論 1 292
  • 正文 為了忘掉前任赌蔑,我火速辦了婚禮娃惯,結(jié)果婚禮上石景,老公的妹妹穿的比我還像新娘拙吉。我一直安慰自己筷黔,他們只是感情好佛舱,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,216評(píng)論 6 388
  • 文/花漫 我一把揭開白布请祖。 她就那樣靜靜地躺著肆捕,像睡著了一般慎陵。 火紅的嫁衣襯著肌膚如雪席纽。 梳的紋絲不亂的頭發(fā)上撞蚕,一...
    開封第一講書人閱讀 51,182評(píng)論 1 299
  • 那天,我揣著相機(jī)與錄音抒和,去河邊找鬼摧莽。 笑死镊辕,一個(gè)胖子當(dāng)著我的面吹牛征懈,可吹牛的內(nèi)容都是我干的揩悄。 我是一名探鬼主播删性,決...
    沈念sama閱讀 40,063評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼维贺,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼溯泣!你這毒婦竟也來了榕茧?” 一聲冷哼從身側(cè)響起用押,我...
    開封第一講書人閱讀 38,917評(píng)論 0 274
  • 序言:老撾萬榮一對(duì)情侶失蹤只恨,失蹤者是張志新(化名)和其女友劉穎官觅,沒想到半個(gè)月后休涤,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,329評(píng)論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡手幢,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,543評(píng)論 2 332
  • 正文 我和宋清朗相戀三年围来,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了监透。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片胀蛮。...
    茶點(diǎn)故事閱讀 39,722評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡粪狼,死狀恐怖再榄,靈堂內(nèi)的尸體忽然破棺而出不跟,到底是詐尸還是另有隱情,我是刑警寧澤吕座,帶...
    沈念sama閱讀 35,425評(píng)論 5 343
  • 正文 年R本政府宣布吴趴,位于F島的核電站锣枝,受9級(jí)特大地震影響兰英,放射性物質(zhì)發(fā)生泄漏畦贸。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,019評(píng)論 3 326
  • 文/蒙蒙 一寨闹、第九天 我趴在偏房一處隱蔽的房頂上張望繁堡。 院中可真熱鬧帖蔓,春花似錦塑娇、人聲如沸埋酬。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,671評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)扎筒。三九已至嗜桌,卻和暖如春辞色,著一層夾襖步出監(jiān)牢的瞬間相满,已是汗流浹背立美。 一陣腳步聲響...
    開封第一講書人閱讀 32,825評(píng)論 1 269
  • 我被黑心中介騙來泰國(guó)打工悯辙, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人击费。 一個(gè)月前我還...
    沈念sama閱讀 47,729評(píng)論 2 368
  • 正文 我出身青樓蔫巩,卻偏偏與公主長(zhǎng)得像圆仔,于是被迫代替她去往敵國(guó)和親坪郭。 傳聞我的和親對(duì)象是個(gè)殘疾皇子歪沃,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,614評(píng)論 2 353

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