kotlin委托與擴(kuò)展

類(lèi)委托

類(lèi)委托:一個(gè)類(lèi)中定義的方法實(shí)際是調(diào)用另一個(gè)類(lèi)的對(duì)象的方法來(lái)實(shí)現(xiàn)驮宴。
DelegatedPattern.kt

  interface PayApi{
      fun pay()
  }

  class AliPay : PayApi{
      override fun pay() {
          println("delegate AliPay")
      }
  }

  //自己實(shí)現(xiàn)的委托
  class ScanCodePay : PayApi{
      private val payApi: PayApi = AliPay()
      override fun pay() {
          payApi.pay()
      }
  }

  fun main(){
      ScanCodePay().pay()
  }

問(wèn)題:假設(shè)接口PayApi 有許多的方法田绑,兩個(gè)實(shí)現(xiàn)類(lèi)都需重寫(xiě)這些方法构罗,那么ScanCodePay的每一個(gè)方法都委托給Alipay嬉橙,代碼冗余 (java實(shí)現(xiàn)可考慮使用動(dòng)態(tài)代理)
在Kotlin中的類(lèi)委托:更加優(yōu)雅亚脆,簡(jiǎn)潔,通過(guò)關(guān)鍵字 by 實(shí)現(xiàn)委托嘹裂。

  //待實(shí)現(xiàn)的接口 + by + 委托對(duì)象
  class ScanCodePay : PayApi by AliPay()

類(lèi)委托實(shí)現(xiàn)原理

通過(guò)將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();
      }
}

編譯器會(huì)自動(dòng)在被委托類(lèi)中添加了一個(gè)委托類(lèi)對(duì)象妄壶,交由委托對(duì)象實(shí)現(xiàn),類(lèi)似委托模式寄狼。

  • 委托模式:有兩個(gè)對(duì)象參與處理同一請(qǐng)求丁寄,則接受請(qǐng)求的對(duì)象將請(qǐng)求委托給另一個(gè)對(duì)象來(lái)處理。
    簡(jiǎn)單來(lái)說(shuō)泊愧,就是操作的對(duì)象不用自己去執(zhí)行操作伊磺,而是將任務(wù)交給另一個(gè)對(duì)象操作。kotlin有類(lèi)委托删咱,屬性委托屑埋。

屬性委托

有一些常見(jiàn)的屬性類(lèi)型,雖然我們可以在每次需要的時(shí)候手動(dòng)實(shí)現(xiàn)它們痰滋, 但是如果能夠?qū)⒁淮涡詫?shí)現(xiàn)并放入一個(gè)庫(kù)會(huì)更方便摘能。例如:

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

為了涵蓋這些情況聪富,kotlin支持屬性委托莺丑。
屬性委托:一個(gè)類(lèi)的某個(gè)屬性值不在類(lèi)中直接定義著蟹,而是將其委托給一個(gè)代理類(lèi)墩蔓,從而實(shí)現(xiàn)對(duì)該類(lèi)的屬性進(jìn)行統(tǒng)一管理。

a. 定義一個(gè)類(lèi)作為屬性委托類(lèi)萧豆,并提供兩個(gè)方法:getValue()奸披、setValue(),他們的方法簽名必須按照如下格式:

// thisRef:進(jìn)行委托的類(lèi)的對(duì)象   property:屬性對(duì)象
operator fun getValue(thisRef: Any?, property: KProperty<*>): T {}
operator fun setValue(thisRef: Any?, property: KProperty<*>, value: T) {} 

b. 在被委托的屬性 后添加:by 委托對(duì)象

val/var <屬性名>: <類(lèi)型> by <委托代理類(lèi)>

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)行委托的類(lèi)的對(duì)象 p 屬性對(duì)象 value 屬性的值
  operator fun setValue(ref: Any?, p: KProperty<*>, value: String) {
      str = value
      println("set ${p.name} --> $str")
  }
}

屬性委托實(shí)現(xiàn)原理

通過(guò)查看java代碼的方式 發(fā)現(xiàn)屬性委托與類(lèi)委托的原理很接近,持有委托類(lèi)的對(duì)象涮雷,并且持有屬性集合阵面,調(diào)用它的getValue()和setValue()方法,對(duì)屬性進(jìn)行讀寫(xiě)洪鸭。
對(duì)于val類(lèi)型的屬性样刷,只需提供一個(gè)getValue()方法即可。

fun main() {
  var bean = Bean()
  bean.name = "Tim"  // setValue set name --> Tim
  bean.name          // getValue get name --> Tim
}

屬性委托的意義體現(xiàn)在它的各種類(lèi)型的屬性上览爵。

延遲屬性

by + lazy + lambda表達(dá)式

LazyPropertyDelegated.kt

val lazyValue: String by lazy {
  println("property lazy")
  "lazyValue"
}

使用場(chǎng)景:延遲val屬性的初始化時(shí)機(jī)(第一次訪問(wèn)的時(shí)候才會(huì)去初始化)
lazyValue 對(duì)象在訪問(wèn)之前置鼻,不會(huì)初始化,狀態(tài)為:Lazy value not initialized yet
第一次訪問(wèn)被委托的屬性時(shí)蜓竹,lambada表達(dá)式會(huì)執(zhí)行一次箕母,并記錄它返回值储藐。之后再訪問(wèn)這個(gè)被委托屬性時(shí),直接使用記錄的返回值嘶是。
使用 lateinit 關(guān)鍵字修飾變量钙勃,處理無(wú)法在構(gòu)造函數(shù)中初始化的變量,避免后續(xù)使用變量時(shí)的判空操作(如: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

這熟悉的使用場(chǎng)景很像單例辖源。單例為了解決多線程同步的問(wèn)題: 有很多方法:靜態(tài)內(nèi)部類(lèi) 、餓漢式等
lazy是如何做到的希太?lazy的實(shí)現(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)部時(shí)通過(guò)同步鎖實(shí)現(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á)式中跛十,通過(guò)添加同步鎖的方式彤路,確保只有一個(gè)線程能夠來(lái)執(zhí)行初始化,確保只被初始化一次芥映。

  • Note: Do not synchronize from external code

lazy是只讀屬性的委托對(duì)象洲尊,查源碼只包含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實(shí)現(xiàn)觀察者模式使用了java標(biāo)準(zhǔn)庫(kù)中的類(lèi)和方法奈偏。但實(shí)現(xiàn)java.util.Observer接口只能重寫(xiě)update方法坞嘀,如果有多種變更邏輯都要體現(xiàn)在update方法里,就要通過(guò)邏輯區(qū)分惊来,使得代碼臃腫丽涩。
示例:ObserverPatternDelegated.kt
額外引入可被觀察的委托屬性,一定程度實(shí)現(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}.")
  }
}

ObserverPatternDelegated.txt

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接收兩個(gè)參數(shù):一個(gè)初始值,賦給被委托屬性枉证;一個(gè)lambda表達(dá)式矮男,
lambda有三個(gè)回調(diào)參數(shù),描述屬性的KProperty室谚、舊值以及新值毡鉴。observable方法的返回值類(lèi)型為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)
    }
}

實(shí)現(xiàn)原理:ReadWriteProperty的實(shí)現(xiàn)類(lèi)為ObservableProperty,委托對(duì)象就是這個(gè)ObservableProperty抽象類(lèi)秒赤。當(dāng) ObservableProperty<T>(initialValue)一旦被委托屬性的值發(fā)生變化(即調(diào)用set方法)時(shí)猪瞬,會(huì)回調(diào)ObservableProperty#setValue方法,在setValue中,調(diào)用了afterChange(),而afterChange的實(shí)現(xiàn)即:onChange 參數(shù)即在使用該委托的時(shí)候傳入的lambda表達(dá)式入篮。
所以每次修改該對(duì)象的值的時(shí)候,都會(huì)調(diào)用傳入的函數(shù),實(shí)現(xiàn)了對(duì)對(duì)象的值改變的觀察.

與之對(duì)應(yīng)的ReadOnlyProperty 只有一個(gè)getValue 方法陈瘦,服務(wù)于val屬性。

同observable委托有類(lèi)似功能的還有一個(gè):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會(huì)的返回值,與observable和vetoable的回調(diào)時(shí)機(jī)不同有關(guān):observable的回調(diào)時(shí)機(jī)是在屬性值修改之后崎弃,vetoable的回調(diào)時(shí)機(jī)在屬性值被修改之前甘晤。(對(duì)應(yīng)setValue方法中的beforeChangeafterChangebeforeChange方法如果返回值為true含潘,屬性值就會(huì)被修改成新值;如果返回值為false线婚,此次修改就會(huì)直接被丟棄遏弱。

map委托

特點(diǎn): 對(duì)于屬性的訪問(wèn),直接委托給一個(gè)map對(duì)象塞弊。
要求: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
}

對(duì)比可以得知:
val的map委托的對(duì)象是Map<String, Any?>,var的map委托的對(duì)象MutableMap<String, Any?>
對(duì)于var屬性游沿,對(duì)于MutableMap中的value的修改饰抒,會(huì)同步到屬性值;反之亦然诀黍。
使用場(chǎng)景:將map中key-value映射到對(duì)象的屬性中,這通常在解析json數(shù)據(jù)時(shí)用到

委托代替多繼承

MultiInhert.kt
java中無(wú)多繼承袋坑,是因?yàn)槎嗬^承的“菱形”問(wèn)題,會(huì)產(chǎn)生歧義眯勾。
而kotlin可以通過(guò)super關(guān)鍵字枣宫,解決多繼承的“菱形”問(wè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() 
    }
}

若同時(shí)實(shí)現(xiàn)多個(gè)接口吃环,且接口間有相同的方法名的默認(rèn)實(shí)現(xiàn)也颤,需要主動(dòng)指定使用的接口方法,或重寫(xiě)方法郁轻。
MultiInhert.txt

委托的優(yōu)點(diǎn)翅娶、使用場(chǎng)景

提供了精簡(jiǎn)的方式來(lái)實(shí)現(xiàn)類(lèi)似java靜態(tài)代理的方式,觀察者模式好唯,以及懶加載的方式灼芭,代替多繼承往衷。

kotlin擴(kuò)展

Kotlin 在不修改類(lèi)源代碼的情況下焕梅,“動(dòng)態(tài)”地為類(lèi)添加屬性(擴(kuò)展屬性)和方法(擴(kuò)展方法)且不需要繼承或使用 Decorator 模式物舒。
擴(kuò)展是一種靜態(tài)行為,對(duì)被擴(kuò)展的類(lèi)代碼本身不會(huì)造成任何影響替蛉。

擴(kuò)展函數(shù)

示例1 給String擴(kuò)展一個(gè)函數(shù)

 // 目標(biāo)類(lèi)型.擴(kuò)展函數(shù)名
fun String.firstChar():String{
...
}

示例2:ExtendMethod.kt 給List擴(kuò)展一個(gè)帶參數(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]
}
extendMethod.png

如何實(shí)現(xiàn)的?
通過(guò)反編譯為java代碼可知:
擴(kuò)展函數(shù)生成了一個(gè)靜態(tài)的方法,當(dāng)Kotlin調(diào)用擴(kuò)展函數(shù)時(shí), 編譯器將會(huì)調(diào)用生成的函數(shù)并且把相應(yīng)的對(duì)象傳入拄氯。
由此也了解到擴(kuò)展函數(shù)不會(huì)帶來(lái)額外的性能消耗躲查。

擴(kuò)展函數(shù)的作用域

通常將擴(kuò)展函數(shù)直接定義在包內(nèi),作用域译柏、調(diào)用方式與java全局靜態(tài)方法類(lèi)似镣煮。
如果定義到類(lèi)的內(nèi)部時(shí),只是相當(dāng)于在擴(kuò)展類(lèi)內(nèi)部的方法鄙麦,通過(guò)反編譯為Java代碼可知典唇,擴(kuò)展方法不再是靜態(tài)方法镊折。

靜態(tài)擴(kuò)展

在kotlin中聲明一個(gè)靜態(tài)擴(kuò)展,則需將其定義在Companion Object
Student.kt

class Student {
    var name: String = ""
    var age: Int = 0
    var no: String = ""

    companion object { //若無(wú)伴生對(duì)象介衔,則需定義
    }
}

fun Student.Companion.changeName(name:String):String {
    ...
}

方便直接靜態(tài)調(diào)用恨胚,無(wú)需創(chuàng)建實(shí)例

擴(kuò)展屬性

kotlin允許動(dòng)態(tài)為類(lèi)擴(kuò)展屬性,擴(kuò)展屬性是通過(guò)添加get炎咖、set方法實(shí)現(xiàn).
示例 ExtendFiled.txt

extendMethod_parmas.png

看java代碼得知:
擴(kuò)展沒(méi)有實(shí)際將成員插入類(lèi)中(沒(méi)有真正的被定義出來(lái))赃泡,只是為該類(lèi)添加get、set方法乘盼。因此擴(kuò)展屬性并沒(méi)有幕后字段(filed)升熊。


demo_extendProperty.png

幕后字段:在Kotlin中, 如果屬性在至少一個(gè)訪問(wèn)器中(getter讀訪問(wèn)器,setter寫(xiě)訪問(wèn)器)使用默認(rèn)實(shí)現(xiàn)绸栅,那么Kotlin會(huì)自動(dòng)提供幕后字段级野,有幕后字段的屬性轉(zhuǎn)換成Java代碼一定有一個(gè)對(duì)應(yīng)的Java變量

擴(kuò)展屬性的限制:

  1. 擴(kuò)展屬性不能有初始值;
  2. val必須提供get方法粹胯,var必須提供get和set方法勺阐。

成員方法優(yōu)先

ExtendAndMember.kt
同名的類(lèi)成員方法的優(yōu)先級(jí)總是高于擴(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
}

可通過(guò)反編譯java代碼查看矛双,在編譯階段已經(jīng)確定渊抽,且Android studio 編譯器會(huì)高亮提示。
此設(shè)計(jì)解決的問(wèn)題:多人開(kāi)發(fā)议忽,各自擴(kuò)展同名方法懒闷,造成不一致的結(jié)果,對(duì)于第三方類(lèi)庫(kù)更甚栈幸。

類(lèi)的實(shí)例與接收者實(shí)例

this的使用在Kotlin中比java靈活愤估。
1.在擴(kuò)展函數(shù)里調(diào)用this,指代的是接收者類(lèi)型(即對(duì)誰(shuí)擴(kuò)展)的實(shí)例速址。
2.在擴(kuò)展函數(shù)內(nèi)部玩焰,想要獲取到類(lèi)的實(shí)例
需要 this@ClassName.extendMethod() 此方式稱(chēng)為:以類(lèi)成員的方式定義擴(kuò)展
在某個(gè)類(lèi)里面為其他類(lèi)定義擴(kuò)展方法、屬性芍锚,該擴(kuò)展的方法昔园,只能在該類(lèi)中通過(guò)被擴(kuò)展的類(lèi)的對(duì)象調(diào)用擴(kuò)展方法。
通過(guò)查看java代碼并炮,發(fā)現(xiàn)不再為static public 而為 public默刚,就如一個(gè)類(lèi)的成員方法

demo_extendMethod.png

擴(kuò)展函數(shù)是靜態(tài)解析的

在編譯時(shí)執(zhí)行,根據(jù)調(diào)用對(duì)象逃魄,方法名找到拓展函數(shù)荤西。
eg:根據(jù)多態(tài)的方式,引用類(lèi)型為父類(lèi),生成子類(lèi)對(duì)象邪锌。但是編譯階段勉躺,靜態(tài)調(diào)用,所以關(guān)注的引用類(lèi)型觅丰。

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! "
}

對(duì)應(yīng)的java代碼

public static final String cook(@NotNull Base $this$cook)  //傳參類(lèi)型取決于引用類(lèi)型

標(biāo)準(zhǔn)庫(kù)中的擴(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是任何類(lèi)型T的通用擴(kuò)展函數(shù)饵溅,執(zhí)行返回類(lèi)型為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()
}

第一個(gè)參數(shù):接收者對(duì)象
第二個(gè)參數(shù):接收者對(duì)象的擴(kuò)展方法,且返回值為R
返回值:第二個(gè)參數(shù)的結(jié)果

    with(Student()) {
        name = "Kitty"  //this.name   student.name
        age = 11
        ageToString()
        no = "010101010"
    }

通過(guò)Java代碼可以看出嚣伐,就是對(duì)傳入?yún)?shù)本身進(jìn)行處理
使用場(chǎng)景: 可調(diào)用一個(gè)對(duì)象的多個(gè)方法

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 為對(duì)象的屬性賦值
場(chǎng)景:構(gòu)造函數(shù),為構(gòu)造對(duì)象初始化屬性轩端,然后返回構(gòu)造對(duì)象放典。eg:自定義View,設(shè)置屬性基茵,再返回這個(gè)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)用類(lèi)的實(shí)例弥臼。

let

let的使用類(lèi)似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ū)別:傳參烙肺,返回值類(lèi)型纳猪,以及this指代。

擴(kuò)展在android中的使用

通過(guò)import kotlinx.android.synthetic.main的方式
取代findViewById
如何實(shí)現(xiàn)的桃笙?

查看Java代碼會(huì)發(fā)現(xiàn)第一次使用控件時(shí)氏堤,在緩存集合中進(jìn)行查找,有則使用搏明,無(wú)則通過(guò)findViewById進(jìn)行查找鼠锈,并將其添加至緩存集合中。而且提供了clearFindViewByIdCache()方法用于清除緩存
Fragment的onDestroyView()方法中默認(rèn)調(diào)用了clearFindViewByIdCache()清除緩存熏瞄,而Activity沒(méi)有脚祟。
由此可見(jiàn),沒(méi)有拋棄使用findViewById()强饮,只是Kotlin的擴(kuò)展插件利用緩存的方式,使得開(kāi)發(fā)更方便为黎、快捷邮丰。

擴(kuò)展的優(yōu)點(diǎn)

擴(kuò)展極大的增加了程序的靈活性行您,簡(jiǎn)潔性。

擴(kuò)展使用場(chǎng)景

在第三方庫(kù)不滿(mǎn)足需求時(shí)剪廉,為遵循開(kāi)閉原則娃循,不修改源碼的方式,對(duì)其進(jìn)行擴(kuò)展斗蒋,java代碼只能使用繼承捌斧,而kotlin直接可以動(dòng)態(tài)的擴(kuò)展,且能更方便組織一些工具方法泉沾。
Google推出的Android擴(kuò)展庫(kù) Android KXT

參考文檔

書(shū)籍《Kotlin核心編程》
Ktolin源碼

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末捞蚂,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子跷究,更是在濱河造成了極大的恐慌姓迅,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,839評(píng)論 6 482
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件俊马,死亡現(xiàn)場(chǎng)離奇詭異丁存,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)柴我,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,543評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門(mén)解寝,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人艘儒,你說(shuō)我怎么就攤上這事聋伦。” “怎么了彤悔?”我有些...
    開(kāi)封第一講書(shū)人閱讀 153,116評(píng)論 0 344
  • 文/不壞的土叔 我叫張陵嘉抓,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我晕窑,道長(zhǎng)抑片,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 55,371評(píng)論 1 279
  • 正文 為了忘掉前任杨赤,我火速辦了婚禮敞斋,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘疾牲。我一直安慰自己植捎,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,384評(píng)論 5 374
  • 文/花漫 我一把揭開(kāi)白布阳柔。 她就那樣靜靜地躺著焰枢,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上济锄,一...
    開(kāi)封第一講書(shū)人閱讀 49,111評(píng)論 1 285
  • 那天暑椰,我揣著相機(jī)與錄音,去河邊找鬼荐绝。 笑死一汽,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的低滩。 我是一名探鬼主播召夹,決...
    沈念sama閱讀 38,416評(píng)論 3 400
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼恕沫!你這毒婦竟也來(lái)了监憎?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 37,053評(píng)論 0 259
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤昏兆,失蹤者是張志新(化名)和其女友劉穎枫虏,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體爬虱,經(jīng)...
    沈念sama閱讀 43,558評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡隶债,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,007評(píng)論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了跑筝。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片死讹。...
    茶點(diǎn)故事閱讀 38,117評(píng)論 1 334
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖曲梗,靈堂內(nèi)的尸體忽然破棺而出赞警,到底是詐尸還是另有隱情,我是刑警寧澤虏两,帶...
    沈念sama閱讀 33,756評(píng)論 4 324
  • 正文 年R本政府宣布愧旦,位于F島的核電站,受9級(jí)特大地震影響定罢,放射性物質(zhì)發(fā)生泄漏笤虫。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,324評(píng)論 3 307
  • 文/蒙蒙 一祖凫、第九天 我趴在偏房一處隱蔽的房頂上張望琼蚯。 院中可真熱鬧,春花似錦惠况、人聲如沸遭庶。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,315評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)峦睡。三九已至翎苫,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間赐俗,已是汗流浹背拉队。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 31,539評(píng)論 1 262
  • 我被黑心中介騙來(lái)泰國(guó)打工弊知, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留阻逮,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 45,578評(píng)論 2 355
  • 正文 我出身青樓秩彤,卻偏偏與公主長(zhǎng)得像叔扼,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子漫雷,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,877評(píng)論 2 345