Kotlin中理解委托属性,并自定义委托

常用的by lazy 延迟委托 , by Delegates.observable() 可观察属性委托, by Delegates.nonNull()等等

语法是: val/var<属性名>:<类型> by<表达式>。在 by后面的表达式是该委托

属性对应的get() (和set())会被委托给它的getValue()和setValue()方法。
所以,kotlin中的代理仅仅是代理了get 和 set 两个方法

属性的委托不必实现任何的接口,但是需要重载操作符getValue()函数(和setValue()——对于var属性)
但是对于val可以实现ReadOnlyProperty,var实现ReadWriteProperty接口(就是帮你实现两个需要重载的操作符的接口)来更快的进行自定义委托.

//此段代码为ReadWriteProperty的接口
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)
}

那么他们究竟是怎么工作的呢?
我们来看看nonNull()委托的代码吧
我初始化了一个Int对象,且用委托表示int1不可以为空
var int1 : Int by Delegates.notNull()
那么我们进入notNull()的代码里查看他的实现
public fun <T: Any> notNull(): ReadWriteProperty<Any?, T> = NotNullVar()
很明显,代理给一个ReadWriteProperty类了
那么NotNullVar()是什么呢

private class NotNullVar<T: Any>() : ReadWriteProperty<Any?, T> {
    private var value: T? = null

    public override fun getValue(thisRef: Any?, property: KProperty<*>): T {
        return value ?: throw IllegalStateException("Property ${property.name} should be initialized before get.")
    }

    public override fun setValue(thisRef: Any?, property: KProperty<*>, value: T) {
        this.value = value
    }
}

我来解释一下,当给int1复制的时候,就会自动调用上面的setValue(),其中的三个参数
第一个thisRef表示持有该对象的对象,即该int1的所有者.
第二个参数 property 是该值的类型,
第三个参数 value 就是属性的值了
在第一次调用setvalue的时候,将该值存到value中,getvalue的时候对value进行是否为空的判断.空就抛异常,这就完成了nonNull的委托


类似的我们看看by Delegates.observable() 这个委托
先看看该委托的使用方法吧

    var int2 : Int by Delegates.observable(1){
        property, oldValue, newValue ->
        //oldvalue是修改前的值,newValue是修改后的值
    }

该委托的实现

    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)
        }

最终委托到的对象,注意这是一个抽象类

public abstract class ObservableProperty<T>(initialValue: T) : ReadWriteProperty<Any?, T> {
    private var value = initialValue

    protected open fun beforeChange(property: KProperty<*>, oldValue: T, newValue: T): Boolean = true


    protected open fun afterChange (property: KProperty<*>, oldValue: T, newValue: T): Unit {}

    public override fun getValue(thisRef: Any?, property: KProperty<*>): T {
        return value
    }

    public override fun setValue(thisRef: Any?, property: KProperty<*>, value: T) {
        val oldValue = this.value //修改前的值

        //beforeChange一直返回true 不知此段代码意义何在
        if (!beforeChange(property, oldValue, value)) {
            return
        }
        this.value = value //修改后的值
        //调用上一段代码中重写的方法
        afterChange(property, oldValue, value)
    }
}

很明显,在setValue的过程中,调用了afterChange(),而afterChange是你使用该代理的时候传入的lambda,所以每次修改该对象的值的时候,都会调用你传入的函数,实现了对对象的值改变的观察.


看完上面两个例子,我们来自定义一个委托用来实现finViewById吧
当然例子很简陋,只能在activity中使用,仅供参考


class MainActivity : AppCompatActivity() ,MainContract.View {

    val imageView : ImageView by bindView(R.id.imageView)

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        imageView.setImageDrawable(null)
    }
}

fun <T: View> bindView( id : Int): FindView<T> = FindView(id)

class FindView<T : View >(val id:Int) : ReadOnlyProperty<Any?,T>{

    override fun getValue(thisRef: Any?, property: KProperty<*>): T {

        if(this.value == null) {
            this.value = (thisRef as Activity).findViewById<T>(id)
        }
        return this.value?:throw RuntimeException("can not find this view")
    }

    var value : T? = null
}

但是,为什么thisref 可以强转成activity呢?上面已经说了thisRef是该对象的持有者,在上面的代码中,即MainActivity.

我们将kotlin代码转成字节码再转到java代码中查看


//反射得到该类的属性
static final KProperty[] $$delegatedProperties = 
new KProperty[]{(KProperty)Reflection.property1(new PropertyReference1Impl(
        Reflection.getOrCreateKotlinClass(MainActivity.class), 
                "imageView", "getImageView()Landroid/widget/ImageView;"))};

  //真正的代理类
   @NotNull
   private final FindView imageView$delegate = MainActivityKt.bindView(2131230801);


  //通过代理类得到imageview
   @NotNull
   public final ImageView getImageView() {
      //在这里,我们就一清二楚的知道了getValue传入的两个参数究竟是什么了!!
      return (ImageView)this.imageView$delegate.getValue(this, $$delegatedProperties[0]);
   }

   protected void onCreate(@Nullable Bundle savedInstanceState) {
      super.onCreate(savedInstanceState);
      this.setContentView(2131361819);
      //通过代理类得到imageview
      this.getImageView().setImageDrawable((Drawable)null);
   }

代理类的实现

public final class FindView implements ReadOnlyProperty {
   @Nullable
   private View value;
   private final int id;

   @NotNull
   public View getValue(@Nullable Object thisRef, @NotNull KProperty property) {
      Intrinsics.checkParameterIsNotNull(property, "property");
      if(this.value == null) {
         if(thisRef == null) {
            throw new TypeCastException("null cannot be cast to non-null type android.app.Activity");
         }

         this.value = ((Activity)thisRef).findViewById(this.id);
      }

      View var10000 = this.value;
      if(this.value != null) {
         return var10000;
      } else {
         throw (Throwable)(new RuntimeException("can not find this view"));
      }
   }

   // $FF: synthetic method
   // $FF: bridge method
   public Object getValue(Object var1, KProperty var2) {
      return this.getValue(var1, var2);
   }

   @Nullable
   public final View getValue() {
      return this.value;
   }

   public final void setValue(@Nullable View var1) {
      this.value = var1;
   }

   public final int getId() {
      return this.id;
   }

   public FindView(int id) {
      this.id = id;
   }
}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,560评论 4 361
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,104评论 1 291
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,297评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,869评论 0 204
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,275评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,563评论 1 216
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,833评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,543评论 0 197
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,245评论 1 241
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,512评论 2 244
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,011评论 1 258
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,359评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,006评论 3 235
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,062评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,825评论 0 194
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,590评论 2 273
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,501评论 2 268

推荐阅读更多精彩内容

  • 简述 在java中一些属性的具有相同的行为怎么办,抽象出类然后再去依赖调用,而在Kotlin中只需要一个by关键字...
    i校长阅读 2,568评论 0 4
  • Kotlin 知识梳理系列文章 Kotlin 知识梳理(1) - Kotlin 基础Kotlin 知识梳理(2) ...
    泽毛阅读 2,590评论 0 6
  • 晨起浇水时与绿叶私语 告诉它昨夜的梦 洗漱时对镜子挤眉弄眼 闭眼给今天的自己笑容 做早餐的功夫也能随音乐摆动 看花...
    白晚安阅读 230评论 5 4
  • 6、在一段时间内,心理学家对一个影响世界上少数居民的现象引起了关注。当他发生时,挪威人称之为“黑暗时期”每...
    邓洁儿阅读 125评论 0 1
  • 晚上十一点,困意袭卷,眼皮开始自觉的耷拉下来,再精彩的文章都已看不下去了,可是脑海里有一个粗重的声音在呼喊:说好今...
    尧月之秀阅读 589评论 8 5