Lambda轉換爲接口對象的細節問題

這裏使用的是Kotlin語言來講解Lambda,和Java中的Lambda是一樣的,之前看Java Lambda的書也有講到這個點,原理是一樣的,這裏記錄一下。

在做Android開發的時候,假設有三個TextView(顯示文本的控件),需要設置同一個點擊事件,Kotlin實現代碼如下:

val onClickListener = { _: View -> 。。。 }
tv_1.setOnClickListener(onClickListener)
tv_2.setOnClickListener(onClickListener)
tv_3.setOnClickListener(onClickListener)

查看Kotlin翻譯成的Java代碼,如下:

Function1 onClickListener = (Function1)(new Function1() {
   public Object invoke(Object var1) {
      。。。
   }

   public final void invoke(@NotNull View $noName_0) {
      。。。
   }
});
tv_1.setOnClickListener((OnClickListener)(new HelpAndFeedbackActivity$sam$android_view_View_OnClickListener$0(onClickListener)));
tv_2.setOnClickListener((OnClickListener)(new HelpAndFeedbackActivity$sam$android_view_View_OnClickListener$0(onClickListener)));
tv_3.setOnClickListener((OnClickListener)(new HelpAndFeedbackActivity$sam$android_view_View_OnClickListener$0(onClickListener)));

這裏我對Java代碼進行了一些刪減,只關注我們需要的代碼,可以看到setOnClickListener的調用,每次都是new的新對象,我們希望傳遞的是同一個對象,如何實現呢,如下:

val onClickListener = View.OnClickListener{ 。。。 }
tv_1.setOnClickListener(onClickListener)
tv_2.setOnClickListener(onClickListener)
tv_3.setOnClickListener(onClickListener)

查看Kotlin翻譯成的Java代碼,如下:

OnClickListener onClickListener = (OnClickListener)(new OnClickListener() {
   public final void onClick(View it) {
      。。。
   }
});
((TextView)this._$_findCachedViewById(id.tv_1)).setOnClickListener(onClickListener);
((TextView)this._$_findCachedViewById(id.tv_2)).setOnClickListener(onClickListener);
((TextView)this._$_findCachedViewById(id.tv_3)).setOnClickListener(onClickListener);

可以看到,三個setOnClickListener的調用傳的是同一個對象。

總結

val onClickListener = { _: View -> 。。。 }
val onClickListener = View.OnClickListener { 。。。 }

上面第一行代碼,聲明的onClickListener變量就是一個Lambda,我們在變量上按Ctrl + Q查看變量的類型如下:
在這裏插入圖片描述
再查看第二行代碼的變量類型,如下:
在這裏插入圖片描述
可以看到,第1個變量類型是Lambda,而第2個變量類型是OnClickListener。

對於View.OnClickListener { },{ }是Lambda,但是View.OnClickListener這個就聲明瞭要把{ }這個轉換爲OnClickListener對象。

所以,我們在平時使用的時候需要注意,如果要把Lambda轉換爲對象,且使用多次,則不要使用第一種方式,因爲它在每次轉換爲對象時都是一個新的對象,特別是需要解除註冊的事件,示例如下:

val lambda = ...
foo.addListener(lambda)
foo.removeListener(lambda)

這種情況會出問題,因爲同一個lambda對象調用了兩次,轉換爲了兩個對象(假設是對象A和對象B),添加和移除的對象不是同一個,所以移除失敗,導致內存泄露。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章