Kotlin 入門(mén)

Kotlin 時(shí)間線(xiàn)

  • 2011年7月,JetBrains 推出 Kotlin 項(xiàng)目
  • 2012年2月,JetBrains 以 Apache 2.0 許可證開(kāi)源此項(xiàng)目,github 地址 kotlin
  • 2016年2月,Kotlin v1.0(第一個(gè)官方穩(wěn)定版本)發(fā)布
  • 2017 Google I/O 大會(huì)上,Kotlin 正式成為 Android 開(kāi)發(fā)官方語(yǔ)言

什么是Kotlin

Kotlin 是由 JetBrains 開(kāi)發(fā)團(tuán)隊(duì)設(shè)計(jì)的基于 JVM 的靜態(tài)型別編程語(yǔ)言

  • JetBrains開(kāi)發(fā)團(tuán)隊(duì): 眾所周知,他們開(kāi)發(fā)的 Intellij IDEA 是開(kāi)發(fā) Java 最優(yōu)秀的 ide。可能是 Java 的局限性迫使他們吸收各個(gè)語(yǔ)言的優(yōu)點(diǎn)來(lái)創(chuàng)造出這門(mén)語(yǔ)言。IDEA(包括Android Studio)對(duì) Kotlin 的支持是非常全面完善的,甚至對(duì) Eclipse 也開(kāi)發(fā)了插件兼容

  • 基于JVM: Kotlin 同 Java 一樣,是編譯成字節(jié)碼,基于 Java Virtual Machine(Java 虛擬機(jī))運(yùn)行的語(yǔ)言。Kotlin 能做到和 Java 混合編譯,這也意味著 Java 豐富的第三方庫(kù)能被 Kotlin 無(wú)條件繼承

  • 靜態(tài)語(yǔ)言: 靜態(tài)類(lèi)型語(yǔ)言是在運(yùn)行前編譯時(shí)檢查類(lèi)型,而不是在運(yùn)行期間檢查數(shù)據(jù)的類(lèi)型。Kotlin 必須清楚定義數(shù)據(jù)類(lèi)型,否則無(wú)法編譯通過(guò),同 Java 一樣,Kotlin 是強(qiáng)靜態(tài)語(yǔ)言,類(lèi)型轉(zhuǎn)換必須顯式表達(dá)

為什么我們要使用 Kotlin

我們可以在這個(gè)網(wǎng)站 https://fabiomsr.github.io/from-java-to-kotlin 上看到 Kotlin 和 Java 的基本語(yǔ)法比較,很明顯 Kotlin 擁有比 Java 更簡(jiǎn)潔的語(yǔ)法

  • 相比較 Java,Kotlin 增加了許多特性,比如 When 表達(dá)式、擴(kuò)展函數(shù)、高級(jí)類(lèi)、委托、Java 8 之前沒(méi)有的 Lambda 表達(dá)式、高階函數(shù)、協(xié)程... 每一個(gè)都讓人興奮不已

  • 相比較 Java,Kotlin 去除了一些特性,比如 final 關(guān)鍵字、靜態(tài)變量、靜態(tài)方法等

  • 相比較 Java,Kotlin 封裝了許多過(guò)程和表示方法,實(shí)現(xiàn)許多語(yǔ)法糖,比如解構(gòu)聲明、操作符重載、空安全... 可以大幅減少代碼,提高效率

列舉一下 Kotlin 純語(yǔ)法上的的好處

  • 開(kāi)源:開(kāi)源的好處其實(shí)也不用多說(shuō),其帶來(lái)的龐大的社區(qū)及活躍的用戶(hù)會(huì)使這門(mén)語(yǔ)言充滿(mǎn)了活力,會(huì)涌現(xiàn)出一大片優(yōu)秀的項(xiàng)目和開(kāi)源庫(kù)

  • Java 的兼容性:Kotlin 的設(shè)計(jì)之初就考慮到了對(duì) Java 代碼的兼容性,現(xiàn)在版本基本上可以實(shí)現(xiàn) 100% 的代碼兼容性,這意味著使用 kotlin 開(kāi)發(fā)的項(xiàng)目可以無(wú)縫調(diào)用已有的 Java庫(kù)和代碼,設(shè)置可以再一個(gè)項(xiàng)目中使用 Java 和 Kotlin 混合編譯。當(dāng)然作為一個(gè) IDE 開(kāi)發(fā)公司,Intellij IDEA 和 Android Studio 對(duì) Kotlin 的支持非常完善,設(shè)置可以一鍵轉(zhuǎn)換代碼

  • 多平臺(tái)開(kāi)發(fā):JetBrains 開(kāi)發(fā) Kotlin 不僅僅想要取代 Java (Android開(kāi)發(fā) —— 官方宣布 Kotlin 正式成為 Android 開(kāi)發(fā)的語(yǔ)言,Web開(kāi)發(fā) —— 對(duì) Spring 框架的支持,以及可以編譯生成 JavaScript 模塊),更遠(yuǎn)大的目標(biāo)使實(shí)現(xiàn)多平臺(tái)的統(tǒng)一,甚至可以進(jìn)行 Native 開(kāi)發(fā),基于 LLVM 底層虛擬機(jī)的實(shí)現(xiàn),Kotlin 可以為各個(gè)平臺(tái)編寫(xiě)原生應(yīng)用,在不久的將來(lái)可以看到 iOS 開(kāi)發(fā)也有 Kotlin 的一席之地

  • 簡(jiǎn)潔安全:Kotlin 的入門(mén)相當(dāng)簡(jiǎn)單,當(dāng)然你有 Java 基礎(chǔ)的話(huà),你可以十分清晰的感受到這門(mén)語(yǔ)言在 Java 的基礎(chǔ)上做了多少優(yōu)化提升,非常值得一試,語(yǔ)法的定義與蘋(píng)果推出的 Swift 有些類(lèi)似,http://nilhcem.com/swift-is-like-kotlin 上可以看到兩者的比較

當(dāng)然后面也會(huì)介紹到一小部分 Kotlin 的特性

kotlin 的語(yǔ)法糖

安全性

空安全

Java 的 NullPointException 一直以來(lái)都是導(dǎo)致程序崩潰的頭號(hào)殺手。kotlin 則通過(guò)類(lèi)型系統(tǒng)旨在代碼中消除潛在的空指針安全問(wèn)題。
在 kotlin 中類(lèi)型系統(tǒng)會(huì)區(qū)分一個(gè)引用可以容納 null (可空引用)還是不能容納(非空引用),通過(guò)在類(lèi)名后添加后綴 實(shí)現(xiàn)

var str1:String = "abc"
// str1 = null 
// String是非空引用,不可以容納 null,編譯器會(huì)報(bào)錯(cuò)

如果需要允許賦值為空,那么我們需要聲明該變量為可空引用,即 Srting?

var str2: String? = "abc"
str2 = null // 編譯通過(guò)

對(duì)于前者,因?yàn)槭欠强找茫覀兛梢灾苯诱{(diào)用對(duì)象屬性而不必考慮空指針的情形

val l = a.length

而對(duì)于后者,因?yàn)槭强煽找茫覀兙筒荒苤苯尤フ{(diào)用對(duì)象屬性,因?yàn)楹苡锌赡軙?huì)導(dǎo)致空指針的出現(xiàn),因此 kotlin 直接杜絕了這種不安全的調(diào)用方式的出現(xiàn)

// val l2 = str2.length
// 編譯錯(cuò)誤:變量 str2 可能為空

安全調(diào)用操作符 ?.

針對(duì)可空引用,kotlin 提供了專(zhuān)門(mén)的空安全調(diào)用操作符 ?.

// var l2: Int = str2?.length
// 編譯錯(cuò)誤:變量 l2 可能為空
var l2: Int? = str2?.length

如果變量 b 為 null,那么返回的也將是 null, 反之則會(huì)返回 b.length 的數(shù)值,所以我們得到的結(jié)果類(lèi)型也是將一個(gè)可空引用 Int?
這個(gè)操作符將在鏈?zhǔn)秸{(diào)用時(shí)發(fā)揮巨大的作用

a?.b?.c?.d // 這種形式的鏈?zhǔn)秸{(diào)用很有可能在一些復(fù)雜的數(shù)據(jù)結(jié)構(gòu)中有用到

當(dāng)鏈?zhǔn)秸{(diào)用的任一節(jié)點(diǎn)為 null 都會(huì)中斷鏈子返回 null,而在 Java 代碼中

if(a != null) {
    if(b != null) {
       if(c != null) {
           return c.d;
       }
    }
}
return null;

這樣的寫(xiě)法即不簡(jiǎn)潔也不美觀(guān),而且最重要的一點(diǎn)是部分開(kāi)發(fā)者并不會(huì)想到這么做
當(dāng)然要只對(duì)非空值執(zhí)行某個(gè)操作,安全調(diào)用操作符可以與 let 一起使用

val listWithNulls: List<String?> = listOf("A", null)
for (item in listWithNulls) {
   item?.let { println(it) } // 輸出 A 并忽略了 null 
}

Elvis 操作符 ?:

當(dāng)一個(gè)可空引用 b 需要如下操作,當(dāng) b 不為空時(shí),我們使用 b,否則我們需要使用一個(gè)非空的默認(rèn)值 c 時(shí),我們就可以使用該操作符

val l = b?.length ?: -1 // 相當(dāng)于 val l: Int = if (b != null) b.length else -1 

如果 ?: 左側(cè)表達(dá)式非空,就返回其左側(cè)表達(dá)式,否則返回右側(cè)表達(dá)式,并且只有當(dāng)左側(cè)的表達(dá)式為空時(shí)才會(huì)對(duì)右側(cè)的表達(dá)式求值

可能導(dǎo)致空指針的操作符 !!

對(duì)于一個(gè)可空引用的變量 b 來(lái)說(shuō),使用該操作符相當(dāng)于返回一個(gè)非空引用的變量 b!!,可以直接使用對(duì)象的屬性,當(dāng)然如果 b 為 null,那么就會(huì)拋出 NullPointException 了

val l = b!!.length // 雖然 b 是可空類(lèi)型,但是可以調(diào)用獲取 length 屬性,但是可能會(huì)拋出空指針異常 

在 kotlin 中除非是你顯示的要求 NullPointException:

  • 顯式調(diào)用 throw NullPointerException()
  • 使用 !! 操作符

否則它不會(huì)不期而至,當(dāng)然還是有兩個(gè)導(dǎo)致空指針異常的原因:

  • 外部 Java 代碼導(dǎo)致
  • 對(duì)于初始化,有一些數(shù)據(jù)不一致(如一個(gè)未初始化的 this 用于構(gòu)造函數(shù)的某個(gè)地方)

類(lèi)型轉(zhuǎn)換安全

如果你檢查類(lèi)型是正確的,編譯器會(huì)為你做自動(dòng)類(lèi)型轉(zhuǎn)換,kotlin 通過(guò) is 操作符及其否定形式 !is 來(lái)檢查對(duì)象是否符合給定的類(lèi)型,相當(dāng)于 Java 中的 instanceof 關(guān)鍵字

同時(shí)我們可以省略顯式的轉(zhuǎn)換操作,因?yàn)榫庉嬈鞲櫜豢勺冎档?is 檢查,并在需要是自動(dòng)插入安全的轉(zhuǎn)換

if(x is String){
    println(x.length)// x會(huì)被編譯器自動(dòng)轉(zhuǎn)化為 Kotlin.String 類(lèi)型,在編譯器上會(huì)有高亮提示
}
//安全轉(zhuǎn)換也可對(duì)反向檢查智能判定
if(x !is String){
    return
}
println(x.length)

//安全轉(zhuǎn)換還可以實(shí)現(xiàn)對(duì) && 和 || 操作符的兼容
if(x is String && x.length > 0) return//對(duì)于 && 后面數(shù)據(jù)類(lèi)型自動(dòng)轉(zhuǎn)換
if(x !is String || x.length == 0) return//對(duì)于 || 后面數(shù)據(jù)類(lèi)型自動(dòng)轉(zhuǎn)換

kotlin 也有顯式的轉(zhuǎn)換操作符 as,如果對(duì)象不是目標(biāo)類(lèi)型,那么常規(guī)類(lèi)型轉(zhuǎn)換可能會(huì)導(dǎo)致 ClassCastException

//類(lèi)型不匹配,會(huì)拋出異常
val x: String = y as String
//上述代碼中,如果 y 為空,也會(huì)拋出異常,所以必須對(duì)空安全兼容
val x: String? = y as String?

當(dāng)然,這種不安全的調(diào)用形式 kotlin 并不提倡,類(lèi)似于空安全操作符,kotlin 提供了一種安全的轉(zhuǎn)換操作符 as?

val x:String? = y as? String //如果類(lèi)型不匹配,則返回 null,所以 x 必須是可空變量,此時(shí)強(qiáng)轉(zhuǎn)類(lèi)型就不必是可空類(lèi)型了

簡(jiǎn)潔性

高級(jí)類(lèi)

Kotlin 提供了許多高級(jí)方法類(lèi),目的就是為了簡(jiǎn)化編程復(fù)雜度

data class 數(shù)據(jù)類(lèi)

Kotlin 將 Java 中專(zhuān)門(mén)用于存放數(shù)據(jù)的類(lèi)用 data 關(guān)鍵字標(biāo)記

data class User(val name: String, val age: Int)

數(shù)據(jù)類(lèi)會(huì)由編譯器自動(dòng)從主構(gòu)造函數(shù)中聲明的所有屬性導(dǎo)出以下成員

  • equals()/hashCode()

  • toString() 格式是 "User(name=John, age=42)"

  • componentN() 解構(gòu)聲明 按聲明順序?qū)?yīng)于所有屬性

    val name = person.component1()
    val age = person.component2()
    

componentN() 函數(shù)運(yùn)用于解構(gòu)聲明,一種類(lèi)似于 python 里元組的操作方式,每個(gè)對(duì)應(yīng)的屬性都有一個(gè)對(duì)應(yīng)的函數(shù),按順序疊加,是 kotlin 廣泛使用的約定原則

val jane = User("Jane", 35)
val (name, age) = jane
println("$name, $age years of age") // 輸出 "Jane, 35 years of age"

在上述代碼中,name 和 age 是調(diào)用 component1() 和 component2() 的返回值

  • copy() 函數(shù)

復(fù)制函數(shù)應(yīng)用于快速生成只有部分屬性不同的相似對(duì)象,其實(shí)現(xiàn)類(lèi)似于

 fun copy(name: String = this.name, age: Int = this.age) = User(name, age)

我們可以實(shí)現(xiàn)

val jack = User(name = "Jack", age = 1)
val olderJack = jack.copy(age = 2)

當(dāng)然,這種方式是淺拷貝

object 單例修飾關(guān)鍵字

單例類(lèi)可以由關(guān)鍵字 object 修飾實(shí)現(xiàn),直接調(diào)用即可
順帶一提,Kotlin 并沒(méi)有提供靜態(tài)方法和屬性,我們需要通過(guò)伴生對(duì)象 companion object 實(shí)現(xiàn)靜態(tài)調(diào)用

enum class 枚舉類(lèi)和 sealed class 密封類(lèi)

枚舉類(lèi)(enum class)與 Java 中的枚舉差距不大,密封類(lèi)(sealed class)在某種程度上是枚舉類(lèi)的一種擴(kuò)展,和枚舉一樣,密封類(lèi)的值是有限集中的類(lèi)型、而不能有任何其他類(lèi)型,只是每個(gè)枚舉常量只存在一個(gè)實(shí)例,而密封類(lèi)的一個(gè)子類(lèi)可以有可包含狀態(tài)的多個(gè)實(shí)例

Type.extension 函數(shù)擴(kuò)展和屬性擴(kuò)展

擴(kuò)展一個(gè)類(lèi)的新功能而無(wú)需繼承該類(lèi)或使用像裝飾者這樣的任何類(lèi)型的設(shè)計(jì)模式。這通過(guò)叫做擴(kuò)展的特殊聲明完成

// 擴(kuò)展方法不可以覆蓋原有方法
fun MutableList<Int>.swap(index1: Int, index2: Int) {
    val tmp = this[index1] // “this”對(duì)應(yīng)該列表
    this[index1] = this[index2]
    this[index2] = tmp
}
// 擴(kuò)展方法調(diào)用
val list = mutableListOf(1, 2, 3)
list.swap(0, 1)
println(list)
// 輸出結(jié)果: [2, 1, 3]


// 擴(kuò)展屬性不可以設(shè)值操作
val Int.isOdd: Boolean
    get() = this and 1 == 1    
// 擴(kuò)展屬性調(diào)用
val n = 3
println(n.isOdd)
// 輸出結(jié)果: true

對(duì)于傳統(tǒng)的工具類(lèi)完全可以是用擴(kuò)展的方法代替,而且 ide 提供了智能聯(lián)想功能,Android 開(kāi)發(fā)中的 kotlin-android-extensions 庫(kù)也是利用了這個(gè)原理

操作符重載

可以先從相等性說(shuō)起,Kotlin 中有兩種類(lèi)型的相等性:

  • 引用相等(兩個(gè)引用指向同一對(duì)象)

引用相等由 === (以及其否定形式 !== )操作判斷。 a === b 當(dāng)且僅當(dāng) a 和 b 指向同 一個(gè)對(duì)象時(shí)求值為 true

  • 結(jié)構(gòu)相等(用 equals() 檢查)

結(jié)構(gòu)相等由 == (以及其否定形式 !=)操作判斷。

a == b 相當(dāng)于 a?.equals(b) ?: (b === null)
// 因?yàn)?== 可能會(huì)出現(xiàn) null == null 的情況,所以會(huì)出現(xiàn)額外的空判斷

當(dāng) a == null 這種判斷會(huì)被直接翻譯成 a === null

Kotlin 允許我們?yōu)樽约旱念?lèi)型提供預(yù)定義的一組操作符的實(shí)現(xiàn)。這些操作符具有固定的符號(hào)表示(如 + 或 *)和固定的優(yōu)先級(jí)。為實(shí)現(xiàn)這樣的操作符,我們?yōu)橄鄳?yīng)的類(lèi)型(即二元操作符左側(cè)的類(lèi)型和一元操作符的參數(shù)類(lèi)型)提供了一個(gè)固定名字的成員函數(shù)或擴(kuò)展函數(shù)。 重載操作符的函數(shù)需要用 operator 修飾符標(biāo)記

比如說(shuō)一元的操作符 ++ ,a++ 和 ++a 都可以翻譯為 a.inc(),當(dāng)然也保留了前后綴的區(qū)別。
后綴 a++ 的過(guò)程是

  • 把 a 的初始值存儲(chǔ)到臨時(shí)存儲(chǔ) a0 中
  • 把 a.inc() 結(jié)果賦值給 a
  • 把 a0 作為表達(dá)式的結(jié)果返回

而前綴 ++a 則是

  • 把 a.inc() 結(jié)果賦值給 a ,
  • 把 a 的新值作為表達(dá)式結(jié)果返回。

可見(jiàn) kotlin 對(duì)特定操作符有做額外的判斷,不僅僅是調(diào)用重載方法,就比如說(shuō)對(duì) == 操作符的 null 判斷的額外處理

二元操作符 +,a + b 類(lèi)似于 a.plus(b) ...

這種重載方法在對(duì)象中以 operator 修飾。當(dāng)然,如果對(duì)象沒(méi)有實(shí)現(xiàn)重載方法怎么辦?當(dāng)然是可以實(shí)現(xiàn)函數(shù)擴(kuò)展啊,只要記得帶上 operator

在這里還有更多更詳細(xì)的關(guān)于操作符重載的知識(shí)
https://www.kotlincn.net/docs/reference/operator-overloading.html

委托

類(lèi)委托

委托模式是在 Android 源碼中廣泛使用的模式,認(rèn)為是實(shí)現(xiàn)繼承的一個(gè)很好的代替模式,當(dāng)無(wú)法或不想直接訪(fǎng)問(wèn)某個(gè)對(duì)象時(shí)就可以通過(guò)一個(gè)代理對(duì)象來(lái)間接訪(fǎng)問(wèn)。Kotlin 可以零樣板代碼地原生支持 它。 類(lèi) Derived 可以繼承一個(gè)接口 Base ,并將其所有共有的方法委托給一個(gè)指定的對(duì)象:

interface Base {
    fun print() 
}

class BaseImpl(val x: Int) : Base {
    override fun print() { print(x) }
}

class Derived(b: Base) : Base by b

fun main(args: Array<String>) { 
    val b = BaseImpl(10) 
    Derived(b).print() // 輸出 10
}
   

by 關(guān)鍵字聲明了 Derived 中將存儲(chǔ) b 變量,并且編譯器將會(huì)把所有的 Base 接口方法都轉(zhuǎn)發(fā)到 b 中執(zhí)行

屬性委托

Kotlin 同時(shí)支持屬性的委托

語(yǔ)法:val/var <property name>: <Type> by <expression>

  • val/var:屬性類(lèi)型(可變/只讀)
  • name:屬性名稱(chēng)
  • Type:屬性的數(shù)據(jù)類(lèi)型
  • expression:代理類(lèi)

by 關(guān)鍵字后面的表達(dá)式是具體代理類(lèi), 因?yàn)閷傩詫?duì)應(yīng)的 getter ( 對(duì)于可變屬性來(lái)說(shuō)還有 setter 方法 ) 會(huì)被委托給它的 getValue() 和 setValue() 方法。 屬性的委托不必實(shí)現(xiàn)任何的接口,但是需要提供一個(gè) getValue() 函數(shù)( 和 setValue() )。 例如:

class Example {
    var p: String by Delegate()
}


class Delegate {
    operator fun getValue(thisRef: Any?, property: KProperty<*>): String {
        return "$thisRef, thank you for delegating '${property.name}' to me!"
    }
    
    operator fun setValue(thisRef: Any?, property: KProperty<*>, value: String) { 
        println("$value has been assigned to '${property.name} in $thisRef.'")
    }
}

如此的話(huà),p 變量的賦值與取值都會(huì)被代理了。
當(dāng)我們從委托到一個(gè) Delegate 實(shí)例的 p 讀取時(shí),將調(diào)用 Delegate 中的 getValue() 函 數(shù), 所以它第一個(gè)參數(shù)是讀出 p 的對(duì)象、第二個(gè)參數(shù)保存了對(duì) p 自身的描述 (例如你可以取它的名字)

val e = Example()
println(e.p)

輸出結(jié)果:
Example@33a17727, thank you for delegating ‘p’ to me!

類(lèi)似地,當(dāng)我們給 p 賦值時(shí),將調(diào)用 setValue() 函數(shù)。前兩個(gè)參數(shù)相同,第三個(gè)參數(shù)保存 將要被賦 的值

e.p = "NEW"

輸出結(jié)果:
NEW has been assigned to ‘p’ in Example@33a17727.

當(dāng)然 kotlin 中標(biāo)準(zhǔn)庫(kù)中包含了可以實(shí)現(xiàn)包含所需 operator 方法的 ReadOnlyProperty(val) 或 ReadWriteProperty(var) 接口,我們只需要繼承實(shí)現(xiàn)對(duì)應(yīng)方法即可

interface ReadOnlyProperty<in R, out T> {
    operator fun getValue(thisRef: R, property: KProperty<*>): T
}

interface ReadWriteProperty<in R, T> {
    operator fun getValue(thisRef: R, property: KProperty<*>): T
    operator fun setValue(thisRef: R, property: KProperty<*>, value: T)
}

另外 kotlin 標(biāo)準(zhǔn)庫(kù)提供了幾種常用方法的工廠(chǎng)方法

  • 延遲屬性(lazy properties): 其值只在首次訪(fǎng)問(wèn)時(shí)計(jì)算
  • 可觀(guān)察屬性(observable properties): 監(jiān)聽(tīng)器會(huì)收到有關(guān)此屬性變更的通知
  • 把多個(gè)屬性?xún)?chǔ)存在一個(gè)映射(map)中,而不是每個(gè)存在單獨(dú)的字段中。

用過(guò)的人都說(shuō)“真TM爽”

如果你想從 Java 的角度去看 kotlin 的語(yǔ)法糖,那么你可以通過(guò)以下方法:

在 Intellij IDEA 或者 Android Studio 中選擇 .kt 文件,然后通過(guò) tools -> Kotlin -> Show Koltin ByteCode 彈窗查看編譯后的字節(jié)碼,你可以通過(guò) Decompile 按鈕查看 Java 代碼

kotlin 語(yǔ)言中文站
Kotlin 學(xué)習(xí)之路

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀(guān)點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

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