Kotlin從入門到掉坑

參考kotlin官方網站Kotlin 語言中文站,該網站還支持在線編寫kotlin代碼,對於沒有kotlin環境的同學來說,非常的方便。
一個有意思的鏈接,從java到kotlin的對比學習https://github.com/MindorksOpenSource/from-java-to-kotlin/blob/master/README-ZH.md

簡介

Kotlin-一個用於現代多平臺應用的靜態編程語言 。Kotlin可以編譯成Java字節碼,支持在J VM上運行;也可以編譯成JavaScript,方便在沒有JVM的設備上運行。Kotlin已正式成爲Android官方支持開發語言。

Kotlin用於原生開發

Kotlin/Native 是一種將 Kotlin 代碼編譯爲無需虛擬機就可運行的原生二進制文件的技術。

爲什麼選用 Kotlin/Native?
Kotlin/Native 的主要設計目標是讓 Kotlin 可以爲不希望或者不可能使用 虛擬機 的平臺(例如嵌入式設備或者 iOS)編譯。 它解決了開發人員需要生成無需額外運行時或虛擬機的自包含程序的情況。

目標平臺
Kotlin/Native 支持以下平臺:

  • iOS(arm32、 arm64、 模擬器 x86_64)
  • MacOS(x86_64)
  • Android(arm32、arm64)
  • Windows(mingw x86_64、x86)
  • Linux(x86_64、 arm32、 MIPS、 MIPS 小端次序、樹莓派)
  • WebAssembly(wasm32)

Kotlin 進行 Android 開發

Kotlin 非常適合開發 Android 應用程序,將現代語言的所有優勢帶入 Android 平臺而不會引入任何新的限制:

  • 兼容性:Kotlin 與 JDK 6 完全兼容,保障了 Kotlin 應用程序可以在較舊的 Android 設備上運行而無任何問題。Kotlin 工具在 Android Studio 中會完全支持,並且兼容 Android 構建系統。
  • 性能:由於非常相似的字節碼結構,Kotlin 應用程序的運行速度與 Java 類似。 隨着 Kotlin 對內聯函數的支持,使用 lambda 表達式的代碼通常比用 Java 寫的代碼運行得更快。
  • 互操作性:Kotlin 可與 Java 進行 100% 的互操作,允許在 Kotlin 應用程序中使用所有現有的 Android 庫 。這包括註解處理,所以數據綁定與 Dagger 也是一樣。
  • 佔用:Kotlin 具有非常緊湊的運行時庫,可以通過使用 ProGuard 進一步減少。 在實際應用程序中,Kotlin 運行時只增加幾百個方法以及 .apk 文件不到 100K 大小。
  • 編譯時長:Kotlin 支持高效的增量編譯,所以對於清理構建會有額外的開銷,增量構建通常與 Java 一樣快或者更快。
  • 學習曲線:對於 Java 開發人員,Kotlin 入門很容易。包含在 Kotlin 插件中的自動 Java 到 Kotlin 的轉換器有助於邁出第一步。Kotlin 心印 通過一系列互動練習提供了語言主要功能的指南。

多平臺項目: iOS 與 Android

在 iOS 與 Android 之間共享 Kotlin 代碼
創建一個 iOS 與一個 Android 應用,來展示 Kotlin 代碼的共享功能。 在 Android 上將使用 Kotlin/JVM,而在 iOS 上將是 Kotlin/Native。

使用

Androidstudio3.0之後自帶kotlin插件,所以可以直接方便使用。
but,還是需要配置一下kotlin相關的,不然會顯示“kotlin not configured”,不能正常使用kotlin。

配置

在Project對應的build.gradle文件裏面添加如下配置:

buildscript {
    ext.kotlin_version = '1.3.31' //kotlin版本號
    
    // ..........
    
    dependencies {
   		 // ..........
        classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" // 添加依賴
    }
}

在Module對應的build.gradle文件裏面添加如下配置:

apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'    // 添加app plugin
apply plugin: 'kotlin-android-extensions'    // 添加app plugin

// ..........

dependencies {
	// ..........
    implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" //添加依賴
}

Kotlin對包體大小的影響

  • 純java項目,我編譯一個空的項目,打包出來的包體大小是1414KB。
  • 純kotlin項目,我編譯一個空的項目,打包出來的包體大小是1990KB。比純java項目的包體大小稍微大一點,因爲引入了一些kotlin相關的庫。
  • 把第一個的純java項目改成兼容kotlin,還是1414KB大小,說明kotlin對包體大小幾乎沒有影響。

基礎語法

  • 函數
    用fun修飾
  • 定義變量
    局部變量使用關鍵字 val 定義
  • 使用字符串模板
    在變量前加$符號
var a = 1
// 模板中的簡單名稱:
val s1 = "a is $a" 
  • 使用區間(range)
    使用 in 運算符來檢測某個數字是否在指定區間內:
val x = 10
val y = 9
if (x in 1..y+1) {
    println("fits in range")
}

檢測某個數字是否在指定區間外:

val list = listOf("a", "b", "c")
if (-1 !in 0..list.lastIndex) {
    println("-1 is out of range")
}
if (list.size !in list.indices) {
    println("list size is out of valid list indices range, too")
}

區間迭代:

for (x in 1..5) {
    print(x)
}

或數列迭代:

for (x in 1..10 step 2) {
    print(x)
}
println()
for (x in 9 downTo 0 step 3) {
    print(x)
}

總結:

for (i in 1..100) { …… }  // 閉區間:包含 100
for (i in 1 until 100) { …… } // 半開區間:不包含 100
for (x in 2..10 step 2) { …… }
for (x in 10 downTo 1) { …… }
if (x in 1..10) { …… }

習慣用法

待定

編碼規範

源代碼組織

  • 源文件名稱
    如果 Kotlin 文件包含單個類(以及可能相關的頂層聲明),那麼文件名應該與該類的名稱相同,並追加 .kt 擴展名。如果文件包含多個類或只包含頂層聲明, 那麼選擇一個描述該文件所包含內容的名稱,並以此命名該文件。使用首字母大寫的駝峯風格 (例如 ProcessDeclarations.kt)。
    文件的名稱應該描述文件中代碼的作用。因此,應避免在文件名中使用諸如“Util”之類的無意義詞語。

命名規則
Kotlin 遵循 Java 命名約定。尤其是:
包的名稱總是小寫且不使用下劃線(org.example.myproject)。 通常不鼓勵使用多個詞的名稱,但是如果確實需要使用多個詞,可以將它們連接在一起或使用駝峯(org.example.myProject)。
類與對象的名稱以大寫字母開頭並使用駝峯:

open class DeclarationProcessor { …… }
object EmptyDeclarationProcessor : DeclarationProcessor() { …… }
  • 函數名
    函數、屬性與局部變量的名稱以小寫字母開頭、使用駝峯而不使用下劃線:
fun processDeclarations() { …… }
var declarationCount = ……
  • 屬性名
    常量名稱(標有 const 的屬性,或者保存不可變數據的沒有自定義 get 函數的頂層/對象 val 屬性)應該使用大寫、下劃線分隔的名稱:
const val MAX_COUNT = 8
val USER_NAME_FIELD = "UserName"

保存帶有行爲的對象或者可變數據的頂層/對象屬性的名稱應該使用常規駝峯名稱:

val mutableCollection: MutableSet<String> = HashSet()

保存單例對象引用的屬性的名稱可以使用與 object 聲明相同的命名風格:

val PersonComparator: Comparator<Person> = ...

對於枚舉常量,可以使用大寫、下劃線分隔的名稱 (enum class Color { RED, GREEN })也可使用以大寫字母開頭的常規駝峯名稱,具體取決於用途。

  • 幕後屬性的名稱
    如果一個類有兩個概念上相同的屬性,一個是公共 API 的一部分,另一個是實現細節,那麼使用下劃線作爲私有屬性名稱的前綴:
class C {
    private val _elementList = mutableListOf<Element>()
    
    val elementList: List<Element>
         get() = _elementList
}
  • 格式化
    在大多數情況下,Kotlin 遵循 Java 編碼規範。
    使用 4 個空格縮進。不要使用 tab。
    對於花括號,將左花括號放在結構起始處的行尾,而將右花括號放在與左括結構橫向對齊的單獨一行。
if (elements != null) {
    for (element in elements) {
        // ……
    }
}

注意:在 Kotlin 中,分號是可選的,因此換行很重要。語言設計採用 Java 風格的花括號格式,如果嘗試使用不同的格式化風格,那麼可能會遇到意外的行爲。

  • 鏈式調用換行
    當對鏈式調用換行時,將 . 字符或者 ?. 操作符放在下一行,並帶有單倍縮進:
val anchor = owner
    ?.firstChild!!
    .siblings(forward = true)
    .dropWhile { it is PsiComment || it is PsiWhiteSpace }

調用鏈的第一個調用通常在換行之前,當然如果能讓代碼更有意義也可以忽略這點。

空安全

可空類型與非空類型
在 Kotlin 中,類型系統區分一個引用可以容納 null (可空引用)還是不能容納(非空引用)。 例如,String 類型的常規變量不能容納 null:

var a: String = "abc"
a = null // 編譯錯誤

如果要允許爲空,我們可以聲明一個變量爲可空字符串,寫作 String?:

var b: String? = "abc"
b = null // ok
print(b)

現在,如果你調用 a 的方法或者訪問它的屬性,它保證不會導致 NPE,這樣你就可以放心地使用:

val l = a.length

但是如果你想訪問 b 的同一個屬性,那麼這是不安全的,並且編譯器會報告一個錯誤:

val l = b.length // 錯誤:變量“b”可能爲空

但是我們還是需要訪問該屬性,對吧?有幾種方式可以做到。
在條件中檢查 null
安全的調用
你的第二個選擇是安全調用操作符,寫作 ?.:

val a = "Kotlin"
val b: String? = null
println(b?.length)
println(a?.length) // 無需安全調用

如果 b 非空,就返回 b.length,否則返回 null
!! 操作符
第三種選擇是爲 NPE 愛好者準備的:非空斷言運算符(!!)將任何值轉換爲非空類型,若該值爲空則拋出異常。我們可以寫 b!! ,這會返回一個非空的 b 值 (例如:在我們例子中的 String)或者如果 b 爲空,就會拋出一個 NPE 異常:

val l = b!!.length

因此,如果你想要一個 NPE,你可以得到它,但是你必須顯式要求它,否則它不會不期而至。
lateinit
在某個類中,如果某些成員變量沒辦法在一開始就初始化,並且又不想使用可空類型(也就是帶?的類型)。那麼,可以使用lateinit來修飾它。不然編譯報錯:

Property must be initialized or be abstract

被lateinit修飾的變量,並不是不初始化,它需要在生命週期流程中進行獲取或者初始化。
如果訪問未初始化的 lateinit 變量會導致

UninitializedPropertyAccessException。

基礎

基本類型

  • 數字
    Kotlin 處理數字在某種程度上接近 Java,但是並不完全相同。例如,對於數字沒有隱式拓寬轉換(如 Java 中 int 可以隱式轉換爲long——譯者注),另外有些情況的字面值略有不同。
    Kotlin 提供瞭如下的內置類型來表示數字(與 Java 很相近):
Type Bit width
Double 64
Float 32
Long 64
Int 32
Short 16
Byte 8

數字字面值中的下劃線(自 1.1 起),你可以使用下劃線使數字常量更易讀:

val oneMillion = 1_000_000
val creditCardNumber = 1234_5678_9012_3456L
val hexBytes = 0xFF_EC_DE_5E
val bytes = 0b11010010_01101001_10010100_10010010
  • 數組
    數組在 Kotlin 中使用 Array 類來表示,它定義了 get 與 set 函數(按照運算符重載約定這會轉變爲 [])以及 size 屬性,以及一些其他有用的成員函數:
class Array<T> private constructor() {
    val size: Int
    operator fun get(index: Int): T
    operator fun set(index: Int, value: T): Unit
​    operator fun iterator(): Iterator<T>
    // ……
}

我們可以使用庫函數 arrayOf() 來創建一個數組並傳遞元素值給它,這樣 arrayOf(1, 2, 3) 創建了 array [1, 2, 3]。 或者,庫函數 arrayOfNulls() 可以用於創建一個指定大小的、所有元素都爲空的數組。

  • When 表達式
    when 取代了類 C 語言的 switch 操作符。其最簡單的形式如下:
when (x) {
    1 -> print("x == 1")
    2 -> print("x == 2")
    else -> { // 注意這個塊
        print("x is neither 1 nor 2")
    }
}

when 將它的參數與所有的分支條件順序比較,直到某個分支滿足條件。 when 既可以被當做表達式使用也可以被當做語句使用。如果它被當做表達式, 符合條件的分支的值就是整個表達式的值,如果當做語句使用, 則忽略個別分支的值。(像 if 一樣,每一個分支可以是一個代碼塊,它的值是塊中最後的表達式的值。)
如果其他分支都不滿足條件將會求值 else 分支。 如果 when 作爲一個表達式使用,則必須有 else 分支, 除非編譯器能夠檢測出所有的可能情況都已經覆蓋了[例如,對於 枚舉(enum)類條目與密封(sealed)類子類型]。
如果很多分支需要用相同的方式處理,則可以把多個分支條件放在一起,用逗號分隔:

when (x) {
    0, 1 -> print("x == 0 or x == 1")
    else -> print("otherwise")
}

我們可以用任意表達式(而不只是常量)作爲分支條件

when (x) {
    parseInt(s) -> print("s encodes x")
    else -> print("s does not encode x")
}

我們也可以檢測一個值在(in)或者不在(!in)一個區間或者集合中:

when (x) {
    in 1..10 -> print("x is in the range")
    in validNumbers -> print("x is valid")
    !in 10..20 -> print("x is outside the range")
    else -> print("none of the above")
}

另一種可能性是檢測一個值是(is)或者不是(!is)一個特定類型的值。注意: 由於智能轉換,你可以訪問該類型的方法與屬性而無需任何額外的檢測。

fun hasPrefix(x: Any) = when(x) {
    is String -> x.startsWith("prefix")
    else -> false
}
  • 標籤處返回
    Kotlin 有函數字面量、局部函數和對象表達式。因此 Kotlin 的函數可以被嵌套。 標籤限制的 return 允許我們從外層函數返回。 最重要的一個用途就是從 lambda 表達式中返回。回想一下我們這麼寫的時候:
fun foo() {
    listOf(1, 2, 3, 4, 5).forEach {
        if (it == 3) return // 非局部直接返回到 foo() 的調用者
        print(it)
    }
    println("this point is unreachable")
}

這個 return 表達式從最直接包圍它的函數即 foo 中返回。 (注意,這種非局部的返回只支持傳給內聯函數的 lambda 表達式。) 如果我們需要從 lambda 表達式中返回,我們必須給它加標籤並用以限制 return。

fun foo() {
    listOf(1, 2, 3, 4, 5).forEach lit@{
        if (it == 3) return@lit // 局部返回到該 lambda 表達式的調用者,即 forEach 循環
        print(it)
    }
    print(" done with explicit label")
}

現在,它只會從 lambda 表達式中返回。通常情況下使用隱式標籤更方便。 該標籤與接受該 lambda 的函數同名。

fun foo() {
    listOf(1, 2, 3, 4, 5).forEach {
        if (it == 3) return@forEach // 局部返回到該 lambda 表達式的調用者,即 forEach 循環
        print(it)
    }
    print(" done with implicit label")
}

類和對象

Kotlin 中使用關鍵字 class 聲明類

class Invoice { ... }

類聲明由類名、類頭(指定其類型參數、主構造函數等)以及由花括號包圍的類體構成。類頭與類體都是可選的; 如果一個類沒有類體,可以省略花括號。

class Empty

構造函數
在 Kotlin 中的一個類可以有一個主構造函數以及一個或多個次構造函數。主構造函數是類頭的一部分:它跟在類名(與可選的類型參數)後。

class Person constructor(firstName: String) { ... }

如果主構造函數沒有任何註解或者可見性修飾符,可以省略這個 constructor 關鍵字。

class Person(firstName: String) { ... }

主構造函數不能包含任何的代碼。初始化的代碼可以放到以 init 關鍵字作爲前綴的初始化塊(initializer blocks)中。
在實例初始化期間,初始化塊按照它們出現在類體中的順序執行,與屬性初始化器交織在一起:

class InitOrderDemo(name: String) {
    val firstProperty = "First property: $name".also(::println)
    init {
        println("First initializer block that prints ${name}")
    }
    
    val secondProperty = "Second property: ${name.length}".also(::println)    
    init {
        println("Second initializer block that prints ${name.length}")
    }
}

請注意,主構造的參數可以在初始化塊中使用。它們也可以在類體內聲明的屬性初始化器中使用:

class Customer(name: String) {
    val customerKey = name.toUpperCase()
}

創建類的實例
要創建一個類的實例,我們就像普通函數一樣調用構造函數:

val invoice = Invoice()
val customer = Customer("Joe Smith")

注意 Kotlin 並沒有 new 關鍵字。
繼承
在 Kotlin 中所有類都有一個共同的超類 Any,這對於沒有超類型聲明的類是默認超類:

class Example // 從 Any 隱式繼承

注意:Any 並不是 java.lang.Object;尤其是,它除了 equals()、hashCode() 與 toString() 外沒有任何成員。
要聲明一個顯式的超類型,我們把類型放到類頭的冒號之後:

open class Base(p: Int)
class Derived(p: Int) : Base(p)

如果派生類有一個主構造函數,其基類型可以(並且必須) 用基類的主構造函數參數就地初始化。
如果類沒有主構造函數,那麼每個次構造函數必須使用 super 關鍵字初始化其基類型,或委託給另一個構造函數做到這一點。 注意,在這種情況下,不同的次構造函數可以調用基類型的不同的構造函數:

class MyView : View {
    constructor(ctx: Context) : super(ctx)​
    constructor(ctx: Context, attrs: AttributeSet) : super(ctx, attrs)
}

覆蓋方法
我們之前提到過,Kotlin 力求清晰顯式。與 Java 不同,Kotlin 對於可覆蓋的成員(我們稱之爲開放)以及覆蓋後的成員需要顯式修飾符:

open class Base {
    open fun v() { ... }
    fun nv() { ... }
}
class Derived() : Base() {
    override fun v() { ... }
}

Derived.v() 函數上必須加上 override 修飾符。如果沒寫,編譯器將會報錯。 如果函數沒有標註 open 如 Base.nv(),那麼子類中不允許定義相同簽名的函數, 不論加不加 override。將 open 修飾符添加到 final 類(即沒有 open 的類)的成員上不起作用。
標記爲 override 的成員本身是開放的,也就是說,它可以在子類中覆蓋。如果你想禁止再次覆蓋,使用 final 關鍵字:

open class AnotherDerived() : Base() {
    final override fun v() { ... }
}

調用超類實現
派生類中的代碼可以使用 super 關鍵字調用其超類的函數與屬性訪問器的實現:

open class Foo {
    open fun f() { println("Foo.f()") }
    open val x: Int get() = 1
}
​class Bar : Foo() {
    override fun f() { 
        super.f()
        println("Bar.f()") 
    }    
    override val x: Int get() = super.x + 1
}

在一個內部類中訪問外部類的超類,可以通過由外部類名限定的 super 關鍵字來實現:super@Outer:

class Bar : Foo() {
    override fun f() { /* …… */ }
    override val x: Int get() = 0    
    inner class Baz {
        fun g() {
            [email protected]() // 調用 Foo 實現的 f()
            println([email protected]) // 使用 Foo 實現的 x 的 getter
        }
    }
}

屬性與字段

聲明屬性
Kotlin 類中的屬性既可以用關鍵字 var 聲明爲可變的,也可以用關鍵字 val 聲明爲只讀的。

class Address {
    var name: String = ……
    var state: String? = ……
}

要使用一個屬性,只要用名稱引用它即可,就像 Java 中的字段:

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

Getters 與 Setters
聲明一個屬性的完整語法是

var <propertyName>[: <PropertyType>] [= <property_initializer>]
    [<getter>]
    [<setter>]

其初始器(initializer)、getter 和 setter 都是可選的。屬性類型如果可以從初始器 (或者從其 getter 返回值,如下文所示)中推斷出來,也可以省略。
例如:

var allByDefault: Int? // 錯誤:需要顯式初始化器,隱含默認 getter 和 setter
var initialized = 1 // 類型 Int、默認 getter 和 setter

一個只讀屬性的語法和一個可變的屬性的語法有兩方面的不同:1、只讀屬性的用 val開始代替var 2、只讀屬性不允許 setter

val simple: Int? // 類型 Int、默認 getter、必須在構造函數中初始化
val inferredType = 1 // 類型 Int 、默認 getter

我們可以爲屬性定義自定義的訪問器。如果我們定義了一個自定義的 getter,那麼每次訪問該屬性時都會調用它 (這讓我們可以實現計算出的屬性)。以下是一個自定義 getter 的示例:

val isEmpty: Boolean
    get() = this.size == 0

如果我們定義了一個自定義的 setter,那麼每次給屬性賦值時都會調用它。一個自定義的 setter 如下所示:

var stringRepresentation: String
    get() = this.toString()
    set(value) {
        setDataFromString(value) // 解析字符串並賦值給其他屬性
}

按照慣例,setter 參數的名稱是 value,但是如果你喜歡你可以選擇一個不同的名稱。
一開始我寫的時候報異常:

Your program produces too much output!

讓我很是懵逼,後來發現在setter 和setter中要用field代替屬性字段,不然就會報錯。
正確寫法:

open class Test(name: String) {
    
    var no = 0
    
    var mName: String = name
    get() = "name is : $field, no is : $no"
    set(value) {
            field = value
            no++
    }
}

錯誤寫法:

    var mName: String = name
    get() = "name is : $mName, no is : $no"  // 應該用field來表示該屬性
    set(value) {
            mName = value // 應該用field來表示該屬性
            no++
    }

編譯期常量
已知值的屬性可以使用 const 修飾符標記爲 編譯期常量。 這些屬性需要滿足以下要求:

  • 位於頂層或者是 object 聲明 或 companion object 的一個成員
  • 以 String 或原生類型值初始化
  • 沒有自定義 getter

這些屬性可以用在註解中:

const val SUBSYSTEM_DEPRECATED: String = "This subsystem is deprecated"
@Deprecated(SUBSYSTEM_DEPRECATED) fun foo() { …… }

函數

函數聲明

Kotlin 中的函數使用 fun 關鍵字聲明:

fun double(x: Int): Int {
    return 2 * x
}

默認參數

函數參數可以有默認值,當省略相應的參數時使用默認值。與其他語言相比,這可以減少重載數量:

fun read(b: Array<Byte>, off: Int = 0, len: Int = b.size) { …… }

默認值通過類型後面的 = 及給出的值來定義。
覆蓋方法總是使用與基類型方法相同的默認參數值。 當覆蓋一個帶有默認參數值的方法時,必須從簽名中省略默認參數值:

open class A {
    open fun foo(i: Int = 10) { …… }
}
​class B : A() {
    override fun foo(i: Int) { …… }  // 不能有默認值
}

如果一個默認參數在一個無默認值的參數之前,那麼該默認值只能通過使用命名參數調用該函數來使用:

fun foo(bar: Int = 0, baz: Int) { …… }
​foo(baz = 1) // 使用默認值 bar = 0

返回 Unit 的函數

如果一個函數不返回任何有用的值,它的返回類型是 Unit。Unit 是一種只有一個值——Unit 的類型。這個值不需要顯式返回:

fun printHello(name: String?): Unit {
    if (name != null)
        println("Hello ${name}")
    else
        println("Hi there!")
    // `return Unit` 或者 `return` 是可選的
}

Unit 返回類型聲明也是可選的。上面的代碼等同於:

fun printHello(name: String?) { …… }

擴展函數

Kotlin 可以對一個類的屬性和方法進行擴展,且不需要繼承或使用 Decorator 模式。
擴展是一種靜態行爲,對被擴展的類代碼本身不會造成任何影響。
擴展函數
擴展函數可以在已有類中添加新的方法,不會對原類做修改,擴展函數定義形式:

fun receiverType.functionName(params){
    body
}
  • receiverType:表示函數的接收者,也就是函數擴展的對象
  • functionName:擴展函數的名稱
  • params:擴展函數的參數,可以爲NULL

舉例:

fun main() {
    var a: Int = 5
    println(a.multiple(3)) // 測試結果是15
}

fun Int.multiple(m: Int) = this*m

對Int類型進行擴展一個倍數的函數,參數就是倍數,直接和本身進行相乘,得出最終結果。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章