1. 程式人生 > 程式設計 >Kotlin 使用高階函式實現回撥方式

Kotlin 使用高階函式實現回撥方式

lambda 和 高階函式

之前學習了 lambda 和高階函式,然後在 android 開發中對 onClick 事件進行監聽是一個很常用的功能,kotlin 的常規實現如下:

  rootView.setOnClickListener { view ->
    println("點選了這個ID=${view.id}的view")
  }

然後在開發中不可避免的我們也要寫一些自定義監聽之類的程式碼。這個時候如果還用 java 的思想去實現的話就有點捨近求遠了。

java 思想實現

在 java 中我們一般的做法是這樣的

定義一個介面
定義一個介面型別變數
定義一個 set 方法

呼叫 set 方法設定介面的實現類

用 kotlin 實現就是如下

class MyView{
  //定義一個介面
  interface IOnLabelCheckedListener {
    fun onLabelCheck(label: String)
  }
  //定義一個介面型別變數
  private var onLabelChecked: IOnLabelCheckedListener? = null

  private fun initView(context: Context) {
    view.setOnCheckedChangeListener { radioGroup,i ->
        onLabelChecked.onLabelCheck(radioGroup.findViewById<RadioButton>(i).text.toString())
    }
  }
  //定義一個 set 方法
  fun setOnLabelCheckedListener(e: IOnLabelCheckedListener) {
    this.onLabelChecked = e
  }
}

   // 呼叫set方法,通過匿名內部類實現
    MyView.setOnLabelCheckedListener(object : LabelBarView.IOnLabelCheckedListener {
      override fun onLabelCheck(label: String) {

      }
    })

這樣實現的問題

當然是太複雜了。而且最初的時候這樣寫一時搞不明白為什麼 MyView.setOnLabelCheckedListener 方法內部不能傳入 lambda 表示式,lambda 表示式的存在不就是為了替代匿名內部類嘛。而且如果這個介面定義的是一個 java 型別的介面就是可以用 lambda 表示式的。這是為什麼?最後猜想是因為 kotlin 在和 java 互相呼叫的時候中間又包裹了一層,而我們直接使用 kotlin 來定義這個介面不存在中間這一層,而我們定義的 set 方法又不是一個高階函式,當然不能使用 lambda 表示式。

下面就用 kotlin 的思想來實現回撥

使用高階函式來實現

kotlin 和 java 有一個重要的不同就是函數語言程式設計。在函數語言程式設計的思想中函式是一等公民,在使用 kotlin 時我們要多利用這種思維來思考問題。Kotlin 中提供了高階函式,它可以直接使用一個函式來作為返回值,對於習慣於 java 來程式設計的我來說剛開始理解起來有些困難,下面我把我一步一步的實現一個高階函式的思路寫下,希望對大家有所幫助。

首先,能想到的就是函式傳遞,要用 lambda 來替代掉匿名內部類可以這樣來實現

//從最基礎的開始做,把匿名內部類通過 lambda 實現
MyView.setOnLabelCheckedListener(object : MyView.IOnLabelCheckedListener {
    override fun onLabelCheck(label: String) {
     println(label)
    }
})
// 首先 MyView.IOnLabelCheckedListener 中只有一個方法 onLabelCheck(label: String)
// 因此可以寫出 lambda 表示式如下
var lam: (String) -> Unit = { label -> println(label) }

然後,需要把寫好的 lambda 傳遞進去,這個時候就要求 setOnLabelCheckedListener 方法是一個高階函式

  // 這裡接收一個 上面我們改造好的表示式 lam,它內部實現應該是把 e 賦值給當前類的一個物件
  fun setOnLabelCheckedListener(e: (String) -> Unit) {
    this.lisenter = e
  }
 
  //顯然 lisenter 就應該是這樣的
  var linsnter: (String) -> Unit = {}

最後使用 linsnter 進行回撥

  private fun initView(context: Context) {
    view.setOnCheckedChangeListener { radioGroup,i ->
      linsnter(radioGroup.findViewById<RadioButton>(i).text.toString())
    }
  }

最終程式碼結果:

class MyView{
  var linsnter: (String) -> Unit = {}

 private fun initView(context: Context) {
    view.setOnCheckedChangeListener { radioGroup,i ->
      linsnter(radioGroup.findViewById<RadioButton>(i).text.toString())
    }
 }

 fun setOnLabelCheckedListener(e: (String) -> Unit) { 
  this.lisenter = e
 }
}
  // 呼叫時將變數 lam 省略,直接使用一個表示式
  view.setOnLabelCheckedListener { label ->
    println(label)
  }

最終的程式碼和之前的程式碼有兩個最大的不同,一是沒有了介面定義,二是沒有了匿名內部類。

更好的使用高階函式

高階函式的使用更多的時候能使我們的程式碼更簡潔,比如下面這段程式碼:

  fun refreshData(e: ((Boolean,String) -> Unit)): Boolean {

    if (!UserInfoManager.getInstance().isLogin) {
      e(false,"未登入")
      return false
    }

    NETWorkUtils.request(ApiParamter(),object : ApiListener<ResponseData> {
      override fun onApiCompleted(data: ResponseData?) {
          e(true,"成功")
      }

      override fun onApiError(errorCode: Int,errorCodeMessage: String) {
         e(false,errorCodeMessage)
      }
    })
    return true
  }

那麼在呼叫它的時候就可以這樣:

   mView.refreshData { isSuccess,msg ->
      //do something
  }

是不是很簡單,省去了再寫一個介面。同時如果是用 java 來呼叫 refreshData 方法也一樣可以的:

    mView.refreshData(new Function2<Boolean,String,Unit>() {
      @Override
      public Unit invoke(Boolean aBoolean,String s) {
        // do something
        return null;
      }
    });

Kotlin 提供了一系列的 Function 介面類來供 java 呼叫高階函式時使用,最多支援22個引數有興趣的可以檢視一下。

以上就是在 Kotlin 中使用高階函式來替代傳統的回撥函式的方法。不對之處還請指正。希望能給大家一個參考,也希望大家多多支援我們。