類委托
類委托:一個類中定義的方法實際是調(diào)用另一個類的對象的方法來實現(xiàn)。
DelegatedPattern.kt
interface PayApi{
fun pay()
}
class AliPay : PayApi{
override fun pay() {
println("delegate AliPay")
}
}
//自己實現(xiàn)的委托
class ScanCodePay : PayApi{
private val payApi: PayApi = AliPay()
override fun pay() {
payApi.pay()
}
}
fun main(){
ScanCodePay().pay()
}
問題:假設(shè)接口PayApi 有許多的方法,兩個實現(xiàn)類都需重寫這些方法,那么ScanCodePay的每一個方法都委托給Alipay,代碼冗余 (java實現(xiàn)可考慮使用動態(tài)代理)
在Kotlin中的類委托:更加優(yōu)雅,簡潔,通過關(guān)鍵字 by 實現(xiàn)委托。
//待實現(xiàn)的接口 + by + 委托對象
class ScanCodePay : PayApi by AliPay()
類委托實現(xiàn)原理
通過將kotlin反編譯為java代碼(Tools->Kotlin->show Kotlin BytesCode ->Decompile)
public final class ScanCodePay implements PayApi {
private final PayApi payApi = (PayApi)(new AliPay());
public void pay() {
this.payApi.pay();
}
}
編譯器會自動在被委托類中添加了一個委托類對象,交由委托對象實現(xiàn),類似委托模式。
- 委托模式:有兩個對象參與處理同一請求,則接受請求的對象將請求委托給另一個對象來處理。
簡單來說,就是操作的對象不用自己去執(zhí)行操作,而是將任務(wù)交給另一個對象操作。kotlin有類委托,屬性委托。
屬性委托
有一些常見的屬性類型,雖然我們可以在每次需要的時候手動實現(xiàn)它們, 但是如果能夠?qū)⒁淮涡詫崿F(xiàn)并放入一個庫會更方便。例如:
- 延遲屬性(lazy properties): 其值只在首次訪問時計算;
- 可觀察屬性(observable properties): 監(jiān)聽器會收到有關(guān)此屬性變更的通知;
- 映射屬性(map properties): 把多個屬性儲存在一個map中,而不是每個存在單獨的字段中。
為了涵蓋這些情況,kotlin支持屬性委托。
屬性委托:一個類的某個屬性值不在類中直接定義,而是將其委托給一個代理類,從而實現(xiàn)對該類的屬性進(jìn)行統(tǒng)一管理。
a. 定義一個類作為屬性委托類,并提供兩個方法:getValue()、setValue(),他們的方法簽名必須按照如下格式:
// thisRef:進(jìn)行委托的類的對象 property:屬性對象
operator fun getValue(thisRef: Any?, property: KProperty<*>): T {}
operator fun setValue(thisRef: Any?, property: KProperty<*>, value: T) {}
b. 在被委托的屬性 后添加:by 委托對象
val/var <屬性名>: <類型> by <委托代理類>
PropertyDelegated.kt
class Bean(){
var name : String by Delegater()
}
class Delegater {
var str : String = ""
operator fun getValue(ref: Any?, p: KProperty<*>) :String {
println("get ${p.name} --> $str")
return str
}
// ref 進(jìn)行委托的類的對象 p 屬性對象 value 屬性的值
operator fun setValue(ref: Any?, p: KProperty<*>, value: String) {
str = value
println("set ${p.name} --> $str")
}
}
屬性委托實現(xiàn)原理
通過查看java代碼的方式 發(fā)現(xiàn)屬性委托與類委托的原理很接近,持有委托類的對象,并且持有屬性集合,調(diào)用它的getValue()和setValue()方法,對屬性進(jìn)行讀寫。
對于val類型的屬性,只需提供一個getValue()方法即可。
fun main() {
var bean = Bean()
bean.name = "Tim" // setValue set name --> Tim
bean.name // getValue get name --> Tim
}
屬性委托的意義體現(xiàn)在它的各種類型的屬性上。
延遲屬性
by + lazy + lambda表達(dá)式
LazyPropertyDelegated.kt
val lazyValue: String by lazy {
println("property lazy")
"lazyValue"
}
使用場景:延遲val屬性的初始化時機(jī)(第一次訪問的時候才會去初始化)
lazyValue 對象在訪問之前,不會初始化,狀態(tài)為:Lazy value not initialized yet
第一次訪問被委托的屬性時,lambada表達(dá)式會執(zhí)行一次,并記錄它返回值。之后再訪問這個被委托屬性時,直接使用記錄的返回值。
使用 lateinit 關(guān)鍵字修飾變量,處理無法在構(gòu)造函數(shù)中初始化的變量,避免后續(xù)使用變量時的判空操作(如:loginBtn?.text=...)
class MyActivity : Activity() {
private lateinit var loginBtn: Button
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
loginBtn = findViewById(R.id.login_button)
}
}
println(lazyValue) 執(zhí)行三次,結(jié)果如下:
property lazy
lazyValue
lazyValue
lazyValue
這熟悉的使用場景很像單例。單例為了解決多線程同步的問題: 有很多方法:靜態(tài)內(nèi)部類 、餓漢式等
lazy是如何做到的?lazy的實現(xiàn)?
LazyJVM.kt 源碼
/**
* Creates a new instance of the [Lazy] that uses the specified initialization function [initializer]
* and the default thread-safety mode [LazyThreadSafetyMode.SYNCHRONIZED].
*
* If the initialization of a value throws an exception, it will attempt to reinitialize the value at next access.
*
* Note that the returned instance uses itself to synchronize on. Do not synchronize from external code on
* the returned instance as it may cause accidental deadlock. Also this behavior can be changed in the future.
*/
public actual fun <T> lazy(initializer: () -> T): Lazy<T> = SynchronizedLazyImpl(initializer)
//查看源碼 得知 SynchronizedLazyImpl方法內(nèi)部時通過同步鎖實現(xiàn)的
···
public actual fun <T> lazy(mode: LazyThreadSafetyMode, initializer: () -> T): Lazy<T> =
when (mode) {
LazyThreadSafetyMode.SYNCHRONIZED -> SynchronizedLazyImpl(initializer)
LazyThreadSafetyMode.PUBLICATION -> SafePublicationLazyImpl(initializer)
LazyThreadSafetyMode.NONE -> UnsafeLazyImpl(initializer)
}
···
LazyThreadSafetyMode.SYNCHRONIZED 這種模式下,lambda表達(dá)式中,通過添加同步鎖的方式,確保只有一個線程能夠來執(zhí)行初始化,確保只被初始化一次。
- Note: Do not synchronize from external code
lazy是只讀屬性的委托對象,查源碼只包含getValue方法。
可觀察屬性
觀察者代碼 ObserverPattern.kt
class StockUpdateOb : Observable() {
val observers = mutableSetOf<Observer>()
fun setStockChanged(price: Double){
observers.forEach{
it.update(this,price)
}
}
}
class StockDisplayOb : Observer{
override fun update(observable: Observable, price: Any) {
if (observable is StockUpdateOb) {
...
println("the latest stock price is ${price}.")
}
else {
...
}
}
}
Kotlin實現(xiàn)觀察者模式使用了java標(biāo)準(zhǔn)庫中的類和方法。但實現(xiàn)java.util.Observer接口只能重寫update方法,如果有多種變更邏輯都要體現(xiàn)在update方法里,就要通過邏輯區(qū)分,使得代碼臃腫。
示例:ObserverPatternDelegated.kt
額外引入可被觀察的委托屬性,一定程度實現(xiàn)了解耦。
class StockUpdate {
private val initialPrice: Double = 10.0
// 觀察者集合
val listeners = mutableSetOf<StockUpdateListener>()
var price : Double by Delegates.observable(initialPrice) { _, oldValue, newValue ->
listeners.forEach {
if (newValue > oldValue) {
it.onRise(price)
}
else {
it.onFall(price)
}
}
}
}
interface StockUpdateListener{
fun onRise(price: Double)
fun onFall(price: Double)
}
class StockDisplay : StockUpdateListener{
override fun onRise(price: Double) {
...
println("the latest stock price has risen to ${price}.")
}
override fun onFall(price: Double) {
...
println("the latest stock price has fall to ${price}.")
}
}
Delegates.observable 源碼
/**
* Returns a property delegate for a read/write property that calls a specified callback function when changed.
* @param initialValue the initial value of the property.
* @param onChange the callback which is called after the change of the property is made. The value of the property
* has already been changed when this callback is invoked.
*
* @sample samples.properties.Delegates.observableDelegate
*/
public inline fun <T> observable(initialValue: T, crossinline onChange: (property: KProperty<*>, oldValue: T, newValue: T) -> Unit):
ReadWriteProperty<Any?, T> =
object : ObservableProperty<T>(initialValue) {
override fun afterChange(property: KProperty<*>, oldValue: T, newValue: T) = onChange(property, oldValue, newValue)
}
使用Delegates.observable()的靈活性,observable接收兩個參數(shù):一個初始值,賦給被委托屬性;一個lambda表達(dá)式,
lambda有三個回調(diào)參數(shù),描述屬性的KProperty、舊值以及新值。observable方法的返回值類型為ReadWriteProperty
ReadWriteProperty源碼
/**
* Base interface that can be used for implementing property delegates of read-write properties.
*/
public interface ReadWriteProperty<in R, T> {
public operator fun getValue(thisRef: R, property: KProperty<*>): T
public operator fun setValue(thisRef: R, property: KProperty<*>, value: T)
}
ObservableProperty源碼
public abstract class ObservableProperty<T>(initialValue: T) : ReadWriteProperty<Any?, T> {
private var value = initialValue
...
public override fun setValue(thisRef: Any?, property: KProperty<*>, value: T) {
val oldValue = this.value
// 之前修改
if (!beforeChange(property, oldValue, value)) {
return
}
this.value = value
// 之后修改
afterChange(property, oldValue, value)
}
}
實現(xiàn)原理:ReadWriteProperty的實現(xiàn)類為ObservableProperty,委托對象就是這個ObservableProperty抽象類。當(dāng) ObservableProperty<T>(initialValue)一旦被委托屬性的值發(fā)生變化(即調(diào)用set方法)時,會回調(diào)ObservableProperty#setValue方法,在setValue中,調(diào)用了afterChange(),而afterChange的實現(xiàn)即:onChange 參數(shù)即在使用該委托的時候傳入的lambda表達(dá)式。
所以每次修改該對象的值的時候,都會調(diào)用傳入的函數(shù),實現(xiàn)了對對象的值改變的觀察.
與之對應(yīng)的ReadOnlyProperty 只有一個getValue 方法,服務(wù)于val屬性。
同observable委托有類似功能的還有一個:vetoable
Delegates.vetoable源碼
public inline fun <T> vetoable(initialValue: T, crossinline onChange: (property: KProperty<*>, oldValue: T, newValue: T) -> Boolean):
ReadWriteProperty<Any?, T> =
object : ObservableProperty<T>(initialValue) {
override fun beforeChange(property: KProperty<*>, oldValue: T, newValue: T): Boolean = onChange(property, oldValue, newValue)
}
lambda會的返回值,與observable和vetoable的回調(diào)時機(jī)不同有關(guān):observable的回調(diào)時機(jī)是在屬性值修改之后,vetoable的回調(diào)時機(jī)在屬性值被修改之前。(對應(yīng)setValue方法中的beforeChange與afterChange)beforeChange方法如果返回值為true,屬性值就會被修改成新值;如果返回值為false,此次修改就會直接被丟棄。
map委托
特點: 對于屬性的訪問,直接委托給一個map對象。
要求:map的key要同屬性名保持一致。
示例MapDelegated.kt
class Account(map: Map<String,Any?>){
val username:String by map
val password:String by map
}
fun mapDelegated(){
val map:Map<String,Any?> = mapOf(
"username" to "Lee",
"password" to "******"
)
val account = Account(map)
//account.username 即 map["username"]
//account.password 即 map["password"]
map["username"] = "" //Compile Error:hint No set method
}
fun mutableMapDelegated() {
val mutableMap: MutableMap<String, Any?> = mutableMapOf(
"name" to "Alice",
"age" to 20,
"address" to "beijing"
)
val student = Student(mutableMap)
student.address = "shanghai"
println("mutableMap address: " + mutableMap["address"]) // shanghai
mutableMap["address"] = "beijing"
println("mutableMap address: " + student.address) // beijing
}
對比可以得知:
val的map委托的對象是Map<String, Any?>,var的map委托的對象MutableMap<String, Any?>
對于var屬性,對于MutableMap中的value的修改,會同步到屬性值;反之亦然。
使用場景:將map中key-value映射到對象的屬性中,這通常在解析json數(shù)據(jù)時用到
委托代替多繼承
MultiInhert.kt
java中無多繼承,是因為多繼承的“菱形”問題,會產(chǎn)生歧義。
而kotlin可以通過super關(guān)鍵字,解決多繼承的“菱形”問題。
interface IPay {
fun pay() = "pay for Cash"
fun change() = "change change change"
}
interface IChange {
fun change() = "give change"
}
open class ScanCodeForPay : IPay {
override fun pay() = "pay for scanCode"
}
open class NoChange: IChange {
override fun change() = "no change"
}
class Transaction(pay:ScanCodeForPay,change: NoChange) : IPay by pay, IChange by change{
override fun change(): String {
return super<IPay>.change()
}
}
若同時實現(xiàn)多個接口,且接口間有相同的方法名的默認(rèn)實現(xiàn),需要主動指定使用的接口方法,或重寫方法。
MultiInhert.txt
委托的優(yōu)點、使用場景
提供了精簡的方式來實現(xiàn)類似java靜態(tài)代理的方式,觀察者模式,以及懶加載的方式,代替多繼承。
kotlin擴(kuò)展
Kotlin 在不修改類源代碼的情況下,“動態(tài)”地為類添加屬性(擴(kuò)展屬性)和方法(擴(kuò)展方法)且不需要繼承或使用 Decorator 模式。
擴(kuò)展是一種靜態(tài)行為,對被擴(kuò)展的類代碼本身不會造成任何影響。
擴(kuò)展函數(shù)
示例1 給String擴(kuò)展一個函數(shù)
// 目標(biāo)類型.擴(kuò)展函數(shù)名
fun String.firstChar():String{
...
}
示例2:ExtendMethod.kt 給List擴(kuò)展一個帶參數(shù)的函數(shù)
fun <T> List<T>.filter(predicate: (T) -> Boolean): MutableList<T> {
val result = ArrayList<T>()
forEach {
if (predicate(it)) {
result.add(it)
}
}
return result
}
fun filterExtend(){
val list = mutableListOf(0,1,2,3,4,5,6,7)
val result = list.filter { it%2 == 0 }
println(result.toString()) //[0, 2, 4, 6]
}

如何實現(xiàn)的?
通過反編譯為java代碼可知:
擴(kuò)展函數(shù)生成了一個靜態(tài)的方法,當(dāng)Kotlin調(diào)用擴(kuò)展函數(shù)時, 編譯器將會調(diào)用生成的函數(shù)并且把相應(yīng)的對象傳入。
由此也了解到擴(kuò)展函數(shù)不會帶來額外的性能消耗。
擴(kuò)展函數(shù)的作用域
通常將擴(kuò)展函數(shù)直接定義在包內(nèi),作用域、調(diào)用方式與java全局靜態(tài)方法類似。
如果定義到類的內(nèi)部時,只是相當(dāng)于在擴(kuò)展類內(nèi)部的方法,通過反編譯為Java代碼可知,擴(kuò)展方法不再是靜態(tài)方法。
靜態(tài)擴(kuò)展
在kotlin中聲明一個靜態(tài)擴(kuò)展,則需將其定義在Companion Object上
Student.kt
class Student {
var name: String = ""
var age: Int = 0
var no: String = ""
companion object { //若無伴生對象,則需定義
}
}
fun Student.Companion.changeName(name:String):String {
...
}
方便直接靜態(tài)調(diào)用,無需創(chuàng)建實例
擴(kuò)展屬性
kotlin允許動態(tài)為類擴(kuò)展屬性,擴(kuò)展屬性是通過添加get、set方法實現(xiàn).
示例 ExtendFiled.txt

看java代碼得知:
擴(kuò)展沒有實際將成員插入類中(沒有真正的被定義出來),只是為該類添加get、set方法。因此擴(kuò)展屬性并沒有幕后字段(filed)。

幕后字段:在Kotlin中, 如果屬性在至少一個訪問器中(getter讀訪問器,setter寫訪問器)使用默認(rèn)實現(xiàn),那么Kotlin會自動提供幕后字段,有幕后字段的屬性轉(zhuǎn)換成Java代碼一定有一個對應(yīng)的Java變量
擴(kuò)展屬性的限制:
- 擴(kuò)展屬性不能有初始值;
- val必須提供get方法,var必須提供get和set方法。
成員方法優(yōu)先
ExtendAndMember.kt
同名的類成員方法的優(yōu)先級總是高于擴(kuò)展函數(shù)。
class Member{
fun getName(){
println("get Member's Name")
}
}
fun Member.getName(){
println("get Member's extend Name")
}
fun readName(){
Member().getName() // get Member's Name
}
可通過反編譯java代碼查看,在編譯階段已經(jīng)確定,且Android studio 編譯器會高亮提示。
此設(shè)計解決的問題:多人開發(fā),各自擴(kuò)展同名方法,造成不一致的結(jié)果,對于第三方類庫更甚。
類的實例與接收者實例
this的使用在Kotlin中比java靈活。
1.在擴(kuò)展函數(shù)里調(diào)用this,指代的是接收者類型(即對誰擴(kuò)展)的實例。
2.在擴(kuò)展函數(shù)內(nèi)部,想要獲取到類的實例
需要 this@ClassName.extendMethod() 此方式稱為:以類成員的方式定義擴(kuò)展
在某個類里面為其他類定義擴(kuò)展方法、屬性,該擴(kuò)展的方法,只能在該類中通過被擴(kuò)展的類的對象調(diào)用擴(kuò)展方法。
通過查看java代碼,發(fā)現(xiàn)不再為static public 而為 public,就如一個類的成員方法

擴(kuò)展函數(shù)是靜態(tài)解析的
在編譯時執(zhí)行,根據(jù)調(diào)用對象,方法名找到拓展函數(shù)。
eg:根據(jù)多態(tài)的方式,引用類型為父類,生成子類對象。但是編譯階段,靜態(tài)調(diào)用,所以關(guān)注的引用類型。
ExtendStaticParse.kt
open class Base
class Extended: Base()
fun Base.cook() = "I'm Base.cook! "
fun Extended.cook() = "I'm Extended.cook! "
fun main() {
val instance: Base = Extended()
val instance2 = Extended()
println(instance.cook()) //"I'm Base.cook! "
println(instance2.cook()) //"I'm Extended.cook! "
}
對應(yīng)的java代碼
public static final String cook(@NotNull Base $this$cook) //傳參類型取決于引用類型
標(biāo)準(zhǔn)庫中的擴(kuò)展函數(shù)
run
/**
* Calls the specified function [block] with `this` value as its receiver and returns its result.
*/
public inline fun <T, R> T.run(block: T.() -> R): R {
...
return block()
}
//使用示例
item.run {
// (this value as its receiver)
holder.tv?.setText(name) //item.name / this.name
}
run是任何類型T的通用擴(kuò)展函數(shù),執(zhí)行返回類型為R的 擴(kuò)展函數(shù)block(),最終返回block()表達(dá)式的結(jié)果。
/**
* Calls the specified function [block] and returns its result.
*/
@kotlin.internal.InlineOnly
public inline fun <R> run(block: () -> R): R {
...
return block()
}
//使用示例 非擴(kuò)展函數(shù)
run {
if(!isLogin)
loginDialog
else
newAccountDialog
}.show()
僅返回表達(dá)式的結(jié)果。
with
/**
* Calls the specified function [block] with the given [receiver] as its receiver and returns its result.
*/
@kotlin.internal.InlineOnly
public inline fun <T, R> with(receiver: T, block: T.() -> R): R {
...
return receiver.block()
}
第一個參數(shù):接收者對象
第二個參數(shù):接收者對象的擴(kuò)展方法,且返回值為R
返回值:第二個參數(shù)的結(jié)果
with(Student()) {
name = "Kitty" //this.name student.name
age = 11
ageToString()
no = "010101010"
}
通過Java代碼可以看出,就是對傳入?yún)?shù)本身進(jìn)行處理
使用場景: 可調(diào)用一個對象的多個方法
Apply、Also
**
* with `this` value as its receiver and returns `this` value.
*/
public inline fun <T> T.apply(block: T.() -> Unit): T {
...
block()
return this
}
/**
* with `this` value as its argument and returns `this` value.
*/
public inline fun <T> T.also(block: (T) -> Unit): T {
...
block(this)
return this
}
二者返回值是函數(shù)的接收者
使用 apply 為對象的屬性賦值
場景:構(gòu)造函數(shù),為構(gòu)造對象初始化屬性,然后返回構(gòu)造對象。eg:自定義View,設(shè)置屬性,再返回這個View
示例 ExtendApplyAndAlsoClass.kt
class ExtendApplyAndAlsoClass {
val student: Student? = getStu()
var age = 11
fun dealStuWithAlso() {
Student.changeName("")
val result = student?.also { stu ->
println(this.age) //11 this == ExtendApplyAndAlsoClass.instance
}
}
fun dealStuWithApply(){
val result = student?.apply {
println(this.age) // 10 this == student
}
}
fun getStu():Student{
var stu: Student = Student()
stu.age = 10
return stu
}
}
區(qū)別:this的指代不同
apply內(nèi)部為擴(kuò)展方法,this指代為接收者。
在aslo內(nèi)部,this指代調(diào)用類的實例。
let
let的使用類似also,但返回值不同
返回值為函數(shù)塊的最后一行或指定return表達(dá)式。
takeIf
/**
* Returns `this` value if it satisfies the given [predicate] or `null`, if it doesn't.
*/
public inline fun <T> T.takeIf(predicate: (T) -> Boolean): T? {
...
return if (predicate(this)) this else null
}
返回值,為boolean或者null 結(jié)合let一起使用
fun getStu():Student{
var stu = Student()
stu.age = 10
return stu
}
val result = getStu().takeIf {
it.age >=18
}?.let { //?容易遺漏
println("go to internet cafe")
}
fun main(){
println(result)
}
以上的方法,區(qū)別:傳參,返回值類型,以及this指代。
擴(kuò)展在android中的使用
通過import kotlinx.android.synthetic.main的方式
取代findViewById
如何實現(xiàn)的?
查看Java代碼會發(fā)現(xiàn)第一次使用控件時,在緩存集合中進(jìn)行查找,有則使用,無則通過findViewById進(jìn)行查找,并將其添加至緩存集合中。而且提供了clearFindViewByIdCache()方法用于清除緩存
Fragment的onDestroyView()方法中默認(rèn)調(diào)用了clearFindViewByIdCache()清除緩存,而Activity沒有。
由此可見,沒有拋棄使用findViewById(),只是Kotlin的擴(kuò)展插件利用緩存的方式,使得開發(fā)更方便、快捷。
擴(kuò)展的優(yōu)點
擴(kuò)展極大的增加了程序的靈活性,簡潔性。
擴(kuò)展使用場景
在第三方庫不滿足需求時,為遵循開閉原則,不修改源碼的方式,對其進(jìn)行擴(kuò)展,java代碼只能使用繼承,而kotlin直接可以動態(tài)的擴(kuò)展,且能更方便組織一些工具方法。
Google推出的Android擴(kuò)展庫 Android KXT
參考文檔
書籍《Kotlin核心編程》
Ktolin源碼