kotlin中by關(guān)鍵字有啥用

前言

在kotlin中饵撑,by關(guān)鍵字代表著代理杨箭,也常常被稱之為委托嗜历。如果了解學(xué)過java設(shè)計模式的同學(xué)應(yīng)該聽說過有個設(shè)計模式叫做代理(委托)設(shè)計模式俱济。在理解kotlin中的by關(guān)鍵字之前,我們不妨先復(fù)習(xí)一下代理模式债蜜。

什么是代理模式

  • 代理模式就是為其他對象提供一種代理以控制對這個對象的訪問晴埂。
    下面是一個簡單的代理模式demo
    package delegate;
    
    interface DelegateApiJava {
        void doSomething();
    }
    
    class ImplJava implements DelegateApiJava {
    
        private DelegateApiJava delegateApiJava;
    
        public ImplJava(DelegateApiJava delegateApiJava) {
            this.delegateApiJava = delegateApiJava;
        }
    
        @Override  
        public void doSomething() {
            if (this.delegateApiJava != null) {
                System.out.println("before");
                delegateApiJava.doSomething();
                System.out.println("after");
            }
        }
    }
    
    public class Demo {
        public static void main(String[] args) {
            ImplJava implJava = new ImplJava(new DelegateApiJava() {
                @Override
                public void doSomething() {
                    System.out.println("doSomething");
                }
            });
    
            implJava.doSomething();
        }
    }
    
    // 輸出(before和after就是自己的邏輯究反,doSomething就是代理對象的實現(xiàn))
    before
    doSomething
    after
    

可以發(fā)現(xiàn),代理模式的本質(zhì)就是儒洛,在實現(xiàn)類中精耐,用代理對象的方法代替實現(xiàn)類中的方法,并適當(dāng)增加一些自己的邏輯琅锻。

koltin中的關(guān)鍵字by

在kotlin中卦停,by關(guān)鍵字主要有兩種用途,一種是接口代理恼蓬,另一種是屬性代理惊完。

接口代理

下面展示一個簡單的接口代理使用方法

package delegate

interface Api {
    fun eat()
    fun play()
}

class ApiImpl(api: Api) : Api by api

為了弄明白by關(guān)鍵字到底做了啥,我們可以點擊 View ->Tool Windows -> kotlin bytecode 查看字節(jié)碼处硬,看不懂的話點一下Decompile小槐,可以看到反編譯之后的java版本源碼下面是這段代碼對應(yīng)的反編譯代碼。

// ApiImpl.java
package delegate;

import kotlin.Metadata;
import kotlin.jvm.internal.Intrinsics;
import org.jetbrains.annotations.NotNull;

@Metadata(
   mv = {1, 1, 16},
   bv = {1, 0, 3},
   k = 1,
   d1 = {"\u0000\u0014\n\u0002\u0018\u0002\n\u0002\u0018\u0002\n\u0002\b\u0003\n\u0002\u0010\u0002\n\u0002\b\u0002\u0018\u00002\u00020\u0001B\r\u0012\u0006\u0010\u0002\u001a\u00020\u0001¢\u0006\u0002\u0010\u0003J\t\u0010\u0004\u001a\u00020\u0005H\u0096\u0001J\t\u0010\u0006\u001a\u00020\u0005H\u0096\u0001¨\u0006\u0007"},
   d2 = {"Ldelegate/ApiImpl;", "Ldelegate/Api;", "api", "(Ldelegate/Api;)V", "eat", "", "play", "qi.main"}
)
public final class ApiImpl implements Api {
   // $FF: synthetic field
   private final Api $$delegate_0;

   public ApiImpl(@NotNull Api api) {
      Intrinsics.checkParameterIsNotNull(api, "api");
      super();
      this.$$delegate_0 = api;
   }

   public void eat() {
      this.$$delegate_0.eat();
   }

   public void play() {
      this.$$delegate_0.play();
   }
}
// Api.java
package delegate;

import kotlin.Metadata;

@Metadata(
   mv = {1, 1, 16},
   bv = {1, 0, 3},
   k = 1,
   d1 = {"\u0000\u0012\n\u0002\u0018\u0002\n\u0002\u0010\u0000\n\u0000\n\u0002\u0010\u0002\n\u0002\b\u0002\bf\u0018\u00002\u00020\u0001J\b\u0010\u0002\u001a\u00020\u0003H&J\b\u0010\u0004\u001a\u00020\u0003H&¨\u0006\u0005"},
   d2 = {"Ldelegate/Api;", "", "eat", "", "play", "qi.main"}
)
public interface Api {
   void eat();

   void play();
}

對比一看荷辕,這不就是設(shè)計模式中的代理模式嗎凿跳,是的,在kotlin中疮方,通過by關(guān)鍵字控嗜,我們可以輕松實現(xiàn)代理模式,幫我們簡化了大量代碼案站,下面看一下屬性代理又是怎么使用的躬审。

屬性代理

屬性代理,顧名思義蟆盐,就是對kotlin中屬性的get和set方法的代理。
屬性代理不需要實現(xiàn)任何方法遭殉,但是他們得提供一個getValue方法(如果是var石挂,還得提供一個setValue方法)。下面是一個簡單的demo

package delegate

import kotlin.reflect.KProperty

class M {
    val s: String by MyDelegate { "Hello" }
}

class MyDelegate<T>(val init: () -> T) {
    operator fun getValue(thisRef: Any?, property: KProperty<*>): T {
        return init()
    }
}

老規(guī)矩险污,使用show kotlin bytecode查看這段代碼到底干了啥

public final class M {
   // $FF: synthetic field
   static final KProperty[] $$delegatedProperties = new KProperty[]{(KProperty)Reflection.property1(new PropertyReference1Impl(Reflection.getOrCreateKotlinClass(M.class), "s", "getS()Ljava/lang/String;"))};
   @NotNull
   private final MyDelegate s$delegate;

   @NotNull
   public final String getS() {
      return (String)this.s$delegate.getValue(this, $$delegatedProperties[0]);
   }

   public M() {
      this.s$delegate = new MyDelegate((Function0)null.INSTANCE);
   }
}
// MyDelegate.java
package delegate;

public final class MyDelegate {
   @NotNull
   private final Function0 init;

   public final Object getValue(@Nullable Object thisRef, @NotNull KProperty property) {
      Intrinsics.checkParameterIsNotNull(property, "property");
      return this.init.invoke();
   }

   @NotNull
   public final Function0 getInit() {
      return this.init;
   }

   public MyDelegate(@NotNull Function0 init) {
      Intrinsics.checkParameterIsNotNull(init, "init");
      super();
      this.init = init;
   }
}

通過對比痹愚,可以清楚的發(fā)現(xiàn),在M類中蛔糯,通過by關(guān)鍵字給M類的屬性生成了一個KProperty的數(shù)組拯腮,當(dāng)然,聲明為數(shù)組是為了支持多個屬性代理蚁飒。在本實例中只給了一個by關(guān)鍵字的屬性代理动壤,所以這個數(shù)組的元素只有一個,表示了被代理對象s的屬性淮逻,然后就是十分類似接口代理的方法補(bǔ)充了琼懊,為s補(bǔ)充get方法阁簸。

思考:為啥得補(bǔ)充getS()這個方法呢

  • koltin通過編譯,能夠自動幫我們實現(xiàn)類中屬性的get和set方法哼丈,幫我們省去了很多事情启妹。但我們在kotlin中沒有寫get/set方法并不代表字節(jié)碼中沒有這倆方法,所以反編譯的結(jié)果中有這個方法醉旦,而這個方法則需要調(diào)用我們自己寫的getValue方法了饶米,這也是為什么屬性代理一定得提供getValue方法的原因了,
  • 是否需要實現(xiàn)setValue取決于屬性是否是用var聲明的车胡。

上面的小例子與by lazy式聲明變量對比如何

kotlin代碼:

package delegate

class M {
    val s by lazy { "hello" }
}

反編譯的java代碼:

public final class M {
   // $FF: synthetic field
   static final KProperty[] $$delegatedProperties = new KProperty[]{(KProperty)Reflection.property1(new PropertyReference1Impl(Reflection.getOrCreateKotlinClass(M.class), "s", "getS()Ljava/lang/String;"))};
   @NotNull
   private final Lazy s$delegate;

   @NotNull
   public final String getS() {
      Lazy var1 = this.s$delegate;
      KProperty var3 = $$delegatedProperties[0];
      boolean var4 = false;
      return (String)var1.getValue();
   }

   public M() {
      this.s$delegate = LazyKt.lazy((Function0)null.INSTANCE);
   }
}

對比可以發(fā)現(xiàn)檬输,僅僅是初始化s$delegate的方法不同而已,這個方法我們可以查看源碼如下:

internal object UNINITIALIZED_VALUE

private class SynchronizedLazyImpl<out T>(initializer: () -> T, lock: Any? = null) : Lazy<T>, Serializable {
    private var initializer: (() -> T)? = initializer
    @Volatile private var _value: Any? = UNINITIALIZED_VALUE
    // final field is required to enable safe publication of constructed instance
    private val lock = lock ?: this

    override val value: T
        get() {
            val _v1 = _value
            if (_v1 !== UNINITIALIZED_VALUE) {
                @Suppress("UNCHECKED_CAST")
                return _v1 as T
            }

            return synchronized(lock) {
                val _v2 = _value
                if (_v2 !== UNINITIALIZED_VALUE) {
                    @Suppress("UNCHECKED_CAST") (_v2 as T)
                } else {
                    val typedValue = initializer!!()
                    _value = typedValue
                    initializer = null
                    typedValue
                }
            }
        }

    override fun isInitialized(): Boolean = _value !== UNINITIALIZED_VALUE

    override fun toString(): String = if (isInitialized()) value.toString() else "Lazy value not initialized yet."

    private fun writeReplace(): Any = InitializedLazyImpl(value)
}

為啥沒有看到getValue方法呢吨拍,不是說好了屬性代理必須實現(xiàn)getValue嗎褪猛?我當(dāng)時看到這個類的時候也納悶了一會兒,還一度把對象的get方法當(dāng)成了getValue方法羹饰,就是下面那個:

override val value: T
    get()

后來轉(zhuǎn)念一想伊滋,這方法參數(shù)也不對啊,這個getValue沒有入?yún)《又龋覀円膅etValue是有兩個入?yún)⒌哪兀?br> 其實getValue是一擴(kuò)展函數(shù)的方式給出的笑旺,源代碼如下。

/**
 * An extension to delegate a read-only property of type [T] to an instance of [Lazy].
 *
 * This extension allows to use instances of Lazy for property delegation:
 * `val property: String by lazy { initializer }`
 */
@kotlin.internal.InlineOnly
public inline operator fun <T> Lazy<T>.getValue(thisRef: Any?, property: KProperty<*>): T = value

總結(jié)一下lazy干了啥把馍资。

  • 1.lazy{"Hello"}是一個實現(xiàn)了Lazy接口的對象筒主,所以有支持屬性代理的getValue方法
  • 2.實現(xiàn)方式默認(rèn)同步,即同一時間只允許一個線程修改value的值
  • 3.懶加載鸟蟹,即有初始化且僅在第一次加載時初始化乌妙,上訴源碼可以看到這點

kotlin用短短一行代碼就解決了java中變量生命的安全性問題,是不是更愛這門語言了呢建钥!

kotlin中的by關(guān)鍵字就寫到這了藤韵,原諒我想到哪寫到哪的低水平文筆,希望能給你幫助_!

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末熊经,一起剝皮案震驚了整個濱河市泽艘,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌镐依,老刑警劉巖匹涮,帶你破解...
    沈念sama閱讀 217,509評論 6 504
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異槐壳,居然都是意外死亡然低,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,806評論 3 394
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來脚翘,“玉大人灼卢,你說我怎么就攤上這事±磁” “怎么了鞋真?”我有些...
    開封第一講書人閱讀 163,875評論 0 354
  • 文/不壞的土叔 我叫張陵,是天一觀的道長沃于。 經(jīng)常有香客問我涩咖,道長,這世上最難降的妖魔是什么繁莹? 我笑而不...
    開封第一講書人閱讀 58,441評論 1 293
  • 正文 為了忘掉前任檩互,我火速辦了婚禮,結(jié)果婚禮上咨演,老公的妹妹穿的比我還像新娘闸昨。我一直安慰自己,他們只是感情好薄风,可當(dāng)我...
    茶點故事閱讀 67,488評論 6 392
  • 文/花漫 我一把揭開白布饵较。 她就那樣靜靜地躺著,像睡著了一般遭赂。 火紅的嫁衣襯著肌膚如雪循诉。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,365評論 1 302
  • 那天撇他,我揣著相機(jī)與錄音茄猫,去河邊找鬼。 笑死困肩,一個胖子當(dāng)著我的面吹牛划纽,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播锌畸,決...
    沈念sama閱讀 40,190評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼阿浓,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了蹋绽?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,062評論 0 276
  • 序言:老撾萬榮一對情侶失蹤筋蓖,失蹤者是張志新(化名)和其女友劉穎卸耘,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體粘咖,經(jīng)...
    沈念sama閱讀 45,500評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡蚣抗,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,706評論 3 335
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片翰铡。...
    茶點故事閱讀 39,834評論 1 347
  • 序言:一個原本活蹦亂跳的男人離奇死亡钝域,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出锭魔,到底是詐尸還是另有隱情例证,我是刑警寧澤,帶...
    沈念sama閱讀 35,559評論 5 345
  • 正文 年R本政府宣布迷捧,位于F島的核電站织咧,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏漠秋。R本人自食惡果不足惜笙蒙,卻給世界環(huán)境...
    茶點故事閱讀 41,167評論 3 328
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望庆锦。 院中可真熱鬧捅位,春花似錦、人聲如沸搂抒。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,779評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽燕耿。三九已至中符,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間誉帅,已是汗流浹背淀散。 一陣腳步聲響...
    開封第一講書人閱讀 32,912評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留蚜锨,地道東北人档插。 一個月前我還...
    沈念sama閱讀 47,958評論 2 370
  • 正文 我出身青樓,卻偏偏與公主長得像亚再,于是被迫代替她去往敵國和親郭膛。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,779評論 2 354

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