內容
- 通用方法toString(),equals(),hashCode()
- 值物件類Data
- 類委託,簡單的單例模式
- Object關鍵字介紹
一 通用方法
為了方便演示,我們首先定義一個Client類如下:
class Client(val name: String, val postalCode: Int) {
}複製程式碼
1.1通用方法toString
在java中如果沒有重寫toString,我們列印出來的直接是物件在記憶體中的地址值。同樣的我們在kotlin中列印的也是地址的值,所以我們類似的可以在類裡邊重寫toString方法:
class Client(val name: String, val postalCode: Int) {
//重寫toString
override fun toString(): String {
return "name${name}postalCode${postalCode}"
}
}複製程式碼
1.2 通用方法equals
java中基本型別可以用==去比較值是否相等,而用在引用型別上比較的是記憶體地址。如果我們想要比較引用型別內的值是否相同,我們要重寫equals,然後呼叫equals去比較值。
而在kotlin中==編譯的時候會編譯成呼叫equals,所以自定義一個類使用==是比較值還是記憶體地址,完全取決於你是否重寫了equals方法。
就如我們上邊定義的類,在沒有重寫equals方法的時候,比較的值物件返回的是false,如果想要比較值,我們必須重寫equals方法:
class Client(val name: String, val postalCode: Int) {
//重寫toString
override fun toString(): String {
return "name${name}postalCode${postalCode}"
}
//重寫後==比較的是內容是否相同,不重寫,比較的是記憶體地址
override fun equals(other: Any?): Boolean {
if (other == null || other !is Client) {
return false
}
return (name==other.name)&&(postalCode==other.postalCode)
}
}複製程式碼
這時候呼叫就是返回的true,比較的是值,而不是記憶體物件。當然如果你建立兩次Client,他們的記憶體地址還是不一樣的,只是值一樣。
這裡我們可以快速的理解基礎一文章中說的為什麼通過不同的方式去建立集合,用==去判斷相等時,有時候相等,有時候不等,原因就在於建立方式是否重寫了equals方法。
1.3 通用方法hashCode
先去找一個問題,還是上邊類去比較:
val client1 = Client("張三", 45000)
val client2 = Client("張三", 45000)
val hashSetOf = hashSetOf<Client>(client1)
val contains = hashSetOf.contains(client2)
Log.e("rrrrrr",""+contains)複製程式碼
返回的是false,原因就在於Contains方法裡邊會先比較他們的hashcode的值,如果相等再去比較,很明顯,你沒有重寫,所以值不想等,返回false。解決方案就是重寫hashCode方法。
class Client(val name: String, val postalCode: Int) {
//重寫toString
override fun toString(): String {
return "name${name}postalCode${postalCode}"
}
//重寫後==比較的是內容是否相同,不重寫,比較的是記憶體地址
override fun equals(other: Any?): Boolean {
if (other == null || other !is Client) {
return false
}
return (name == other.name) && (postalCode == other.postalCode)
}
//重寫hashCode方法,讓集合是否包含也返回true
override fun hashCode(): Int = name.hashCode() + postalCode
}複製程式碼
到此三個常用的方法已經講完畢。
我們知道在java的IDE中我們可以通過模版去生成這三個方法,雖然不用手寫,但是放到類裡邊還是看著不舒服。在Kotlin中你不必再去生成這些方法了。如果為你的類新增data修飾符,必要的方法將會隱式自動生成好。
二 值物件類data
2.1值物件的寫法和隱藏的toString ,equals和hashCode範圍
data class Client(val name: String, val postalCode: Int) 複製程式碼
簡單一個修飾符,就隱式的重寫了所有標準Java方法。
注意:生成的toString ,equals和hashCode僅僅只包括主構造方法中的屬性,沒有在主構造中宣告的不會涵蓋進去。
2.2資料類和不可變性:copy()方法
原因:
1.非同步安全
2.hashMap容器的優化,更加不容易出錯
不可變的宣告又會導致出現一個新的問題,如果需求要求改變值,那麼就送你一首涼涼?事實上Kotlin為了解決這個問題又給你提供一個新的方法copy方法。目的就是想要改變值的時候呼叫,使用方法:
val client1 = Client("張三", 45000)
//這樣寫是錯誤的
//client1.name = "李四"
//需要這樣去修改值,內部其實是建立一個新的client物件
client1.copy(name = "李四")複製程式碼
原理實現大概如下:
data class Client(val name: String, val postalCode: Int) {
//虛擬碼
fun copy2(name :String = this.name,postalCode:Int = this.postalCode)= Client(name,postalCode)
}複製程式碼
至此data類算是陳述完畢,可能有人會想到,實際開發中,資料那麼多,難道我一個一個寫?有沒有像java那樣自動生成的外掛:我個人用的是JsonToKotlinClass自動生成的data類。
三類委託
java中的裝飾者模式或者叫代理模式:模式的本質就是建立一個新類,實現與原始類一樣的介面並將原來的類的例項作為一個欄位儲存。與原始類擁有同樣行為的方法不用被修改,只需要呼叫原始方法,並新增你需要新增的邏輯。
按照這個方式我們用java的思想kotlin的程式碼去實現一下:
//定義登入介面
interface ILogin {
fun login(name: String, pwd: String)
}
複製程式碼
class Login : ILogin {
override fun login(name: String, pwd: String) {
Log.e("rrrrr", "拿著使用者名稱和密碼去請求網路")
}
}複製程式碼
結果發現沒有做非空判斷,我們可以使用代理模式,類似這樣的方式去擴充套件:
class ExtendLogin(val obj: Login) : ILogin {
override fun login(name: String, pwd: String) {
if (name.isEmpty() || pwd.isEmpty()) {
Log.e("rrrr", "告訴使用者沒有輸入帳號或密碼")
} else {
obj.login(name, pwd)
}
}
}複製程式碼
上邊是純java思想的程式碼,那麼Kotlin中的委託模式給我們提供了什麼更簡潔的方式呢?
這裡要介紹一個關鍵字by,用於類的委託。我們可以把擴充套件程式碼寫成這樣:
class ExtendLogin(val obj: Login) : ILogin by Login(){
}複製程式碼
這就完全讓另外一個類去實現這個方法,但是擴充套件的內容卻沒有地方去寫了,那麼我們繼續重寫介面定義的方法
class ExtendLogin(val obj: Login) : ILogin by Login(){
override fun login(name: String, pwd: String) {
if (name.isEmpty() || pwd.isEmpty()) {
Log.e("rrrr", "告訴使用者沒有輸入帳號或密碼")
} else {
obj.login(name, pwd)
}
}
}複製程式碼
這樣就又可以從新寫,但是卻發現問題:
- by不by這個子類物件好像沒有什麼卵用。
- 且傳遞過來的物件和by的物件不是一個物件。
解釋問題1:在這個例子中確實沒有什麼卵用。如果一個介面定義了N個方法,且你只需要去修改一個方法裡邊的邏輯,你使用by,會大大減少重複的程式碼。有人可能會說直接繼承重寫單獨的一個方法buiu行了?這麼費勁幹什麼,其實不然,繼承和包裝是完全不同的原理。
解釋問題2:這裡也是是涉及到單例模式,和上一節的私有構造,然後單例幾乎是同一個問題。上一篇我們用私有構造方法+companion object關鍵字+自定義屬性,非常辛苦的完成一個單例模式,這裡給大家介紹一個更加簡單的單例模式:
把Login類的class關鍵字替換成object關鍵字,這個時候Login類就變成了單例模式!這個時候單例的物件就是類的名字。這裡我可把所有Login()換成Login。
四 Object關鍵字
通過上邊的單例模式我們已經簡單瞭解了關於object關鍵字的威力,他還有很多其他作用,但是它們都遵循同樣的核心理念:這個關鍵字定義一個類並同時建立一個例項(換句話說就是一個物件)。使用場景有:
- 定義單例的一種方式
- 物件表示式用來替代Java的匿名內部類。
- 伴生物件可以持有工廠方法和其他與這個類相關,但在呼叫時並不依賴類例項的方法。它們的成員可以通過類名來訪問。
4.1單例模式已經說過,這裡不在陳述。
4.2伴生物件
4.2.1介紹
還記得我們上一篇複雜的實現單例模式的程式碼嗎?那就是伴生物件。伴生物件寫在類的內部,可以訪問類中的所有private成員,包括private構造方法。伴生物件成員在子類中不能被重寫。
4.2.2伴生物件實現介面和擴充套件方法和屬性
有名字的伴生物件的寫法
class Person {
companion object Loader {
}
}複製程式碼
實現介面
在實際開發的時候,我們會遇到很多String轉換成物件的情況(當然我們在使用Retrofit+Rxjava之後請求之後的這種轉化已經不需要),假如我們定義一個介面去統一這種功能:
interface JSONFactory<T> {
fun formJson(Json: String): T
}複製程式碼
對應伴生物件去實現如下:
class Person {
companion object Loader :JSONFactory<Person>{
override fun formJson(Json: String): Person {
return Gson().fromJson(Json,Person::class.java)
}
}
}複製程式碼
呼叫傳入String返回person物件
val formJson = Person.Loader.formJson("")複製程式碼
擴充套件方法和屬性:
擴充套件的方法和第二篇講解的類的擴充套件方法和屬性一樣,上邊程式碼可以修改成這樣:
class Person {
companion object {
}
}
fun Person.Companion.formJson(Json: String) :Person{
return Gson().fromJson(Json,Person::class.java)
}複製程式碼
呼叫方式一樣,Person類的結構更加明朗。
4.3匿名內部類
一個按鈕的點選事件,可以有兩種方法去實現
tv.setOnClickListener { }
tv.setOnClickListener(object :View.OnClickListener{
override fun onClick(v: View?) {
}
})複製程式碼
小結
data資料類的隱式實現的四個方法的基本邏輯,==和java的equals方法一樣
代理模式的寫法
object去實現單例
伴生物件的寫法和擴充套件
匿名類的寫法