Kotlin委託 & 擴充套件 & 高階函式

xiasuhuei321發表於2017-12-13

寫在前面

Kotlin現在已經是Android官方的一級開發語言了,以前就有大佬給我安利,最近剛好看open cv的c++和ndk看的頭昏腦漲,反正最近也用不到,只是出於興趣,不如換個腦子看看最近勢頭比較盛的Kotlin好了。在這裡感謝一下貓哥對我的耐心指導,讓我對Kotlin的認識更進了一步。

委託

委託有委託類和委託屬性。

委託類

我在看文件的時候就感覺跟Java裡的某個操作非常像……於是非常惡趣味的將程式碼寫成了如下模式:

interface OnClickListener {
   fun click()
}
​
class MyListener : OnClickListener {
   override fun click() {
       println("do something about click event")
   }
}
​
class MainActivity(b: OnClickListener) : OnClickListener by b {
   init {
       println("we can do something about activity here")
   }
}
​
fun main(args: Array<String>) {
   val listener = MyListener()
   val activity = MainActivity(listener)
   activity.click()
}
​
// 輸出:
// we can do something about activity here
// do something about click event
複製程式碼

是的,我覺得這個類委託跟Java裡設定介面回撥這個操作很像。只不過一般情況下,我們會使用匿名類來實現這個介面,並在實現中寫上我們的程式碼。而委託類在文件中的描述是:The Delegation pattern has proven to be a good alternative to implementation inheritance, and Kotlin supports it natively requiring zero boilerplate code.

又到了展示我辣雞英語水平的時候了:委託模式是一種實現繼承的良好方式,Kotlin原生支援不需要額外的樣板程式碼。(意譯,不完全符合字面意思)

也就是說跟我想的差不多,Kotlin語言本身提供了委託,可以讓我們省了setxxxListener這種重複勞動。

委託屬性

委託屬性官方舉了三個應用場景:

  • 延遲屬性:只在第一次訪問的時候初始化(計算)

  • 可觀察屬性:監聽器得到關於這個特性變化的通知

  • 把所有屬性儲存在一個map中,而不是在單獨的欄位裡

假如現在在專案裡需要一個地點名稱的屬性,這個地點並不是每一次都需要用到,我們只在有需要的時候獲取這個屬性,我們就可以使用Kotlin提供的lazy來實現:

   val address: String by lazy {
       getAdd()
   }
​
   fun getAdd(): String = "北京天安門"
複製程式碼

下面是可觀察屬性,Kotlin中提供了Observable委託,來看看是怎麼用的:

class PropertyDelegate {

   var status: Int by Delegates.observable(0) {
       d, old, new ->
       println("屬性名稱:$d,以前是:$old,現在是:$new")
   }
}
​
fun main(args: Array<String>) {
   var p = PropertyDelegate()
​
   println(p.status)
   p.status = 2
}
// 輸出:
// 0
// 屬性名稱:var PropertyDelegate.status: kotlin.Int,以前是:0,現在是:2
複製程式碼

的確觀察到了屬性值的變化,上面observable(0)中的0作為status的初始化值,後面的那種寫法暫時先放著,是將函式作為引數的一種使用方式。 接下來看看將屬性儲存在Map中:

class TestUser(val map: Map<String, Any?>) {

   val name: String by map
   val age: Int by map
}
​
fun main(args: Array<String>) {
   var user: TestUser = TestUser(mapOf(
           "name" to "test",
           "age" to 16
   ))
​
   println("username:" + user.name + ",age:" + user.age)
}
// 輸出:
// username:test,age:16
複製程式碼

上面三個都是Kotlin提供的委託,我們也可以自己實現屬性委託:

class Http {

   var name: String? = nullfun getNameInfoFromNetWork(): String {
       return name ?: "假裝我是從網路獲取的名字"
   }
​
   operator fun getValue(any: Any, property: KProperty<*>): String {
       println("列印觀察:any=$any property=$property")
       return getNameInfoFromNetWork()
   }
​
   operator fun setValue(any: Any, property: KProperty<*>, s: String) {
       this.name = s
   }
}
​
class PropertyDelegate {
   var name: String by Http()
​
   val address: String by lazy {
       getAdd()
   }
​
   fun getAdd(): String = "北京天安門"var status: Int by Delegates.observable(0) {
       d, old, new ->
       println("屬性名稱:$d,以前是:$old,現在是:$new")
   }
​
​
}
​
fun main(args: Array<String>) {
   var p = PropertyDelegate()
   println(p.name)
   p.name = "hh"
   println(p.name)
}
// 輸出:
// 列印觀察:any=PropertyDelegate@2e5d6d97 property=var PropertyDelegate.name: kotlin.String
// 假裝我是從網路獲取的名字
// 列印觀察:any=PropertyDelegate@2e5d6d97 property=var PropertyDelegate.name: kotlin.String
// hh
複製程式碼

var宣告的是可變數,需要實現set和get,而val是不可變數,只需要實現get。

簡單的介紹了一下官方列舉的這三個應用場景,其實感覺這三個例子並不能戳中我的痛點,因為現有的Java雖然實現起來囉嗦了點,但也並不是非常麻煩。比如可觀察的屬性,我只要在將屬性宣告為private,然後在set方法裡設定一個介面回撥就可以了,介面再弄個泛型,得了,所有可觀察屬性都能用這個介面了。至於後面兩個也是同樣的,但是這是語言層面的直接支援,餵你糖吃總是得心懷感激的,總好過吃翔不是。接下來就來了解一下Kotlin比較牛x的兩個東西:擴充套件和高階函式。

擴充套件 & 高階函式

擴充套件是Kotlin中比較能打動我的一個特性,比如原來的Android中的Toast需要這麼寫:

Toast.makeText(mContext,"toast",Toast.LENGTH_SHORT).show();
複製程式碼

說實話,剛開始還覺得敲著挺帶感,慢慢的就煩了,自己簡單的封裝一下是這樣:

class ToastUtil{
 public static void show(Context context,String text){
  Toast.makeText(context,text,Toast.LENGTH_SHORT).show();
 }
}
複製程式碼

後來傳context也傳煩了,進一步封裝,在Application里弄一個靜態方法獲取context,然後:

class ToastUtil{

 public static void show(String text){
  Toast.makeText(Application.getContext(),text,Toast.LENGTH_SHORT).show();
 }
}
複製程式碼

那麼利用Kotlin的擴充套件,可以怎麼做呢?

fun Context.toast(text: String){
   Toast.makeText(this,text,Toast.LENGTH_SHORT).show()
}
複製程式碼

這為Context類擴充套件了一個toast方法,這樣以後你可以這樣使用:

context.toast("hello")
複製程式碼

如果你已經在上下文中:

toast("hello")
複製程式碼

這可以說是十分的好用了,有了擴充套件,還要什麼工(zi)具(xing)類(che)。擴充套件的語法比較簡單,不多贅述。另外值得注意的是擴充套件並非繼承,沒辦法複寫類中的函式,用以下例子來說明一下:

class Extend {
   fun hello() {
       println("hello")
   }
}
​
fun Extend.hello(s: String) {
   println("$s hello")
}
​
fun Extend.hello() {
   println("無參 擴充套件 hello")
}
​
fun main(args: Array<String>) {
   val e = Extend()
   e.hello()
   e.hello("luo")
}
// 輸出:
// hello
// luo hello
複製程式碼

可以看到當引數和方法名一樣時,呼叫的是類內部的方法並非擴充套件方法,當引數不一致時呼叫對應的方法。

高階函式

高階,這倆字一看就高階大氣上檔次,看一下文件的描述:A higher-order function is a function that takes functions as parameters, or returns a function。高階函式一種能把函式作為引數,或者將函式作為返回值的函式。將函式作為引數,橋豆麻袋,這……這不是跟傳遞點選事件那玩意很像麼!Java裡寫的是介面回撥,Java8 可以用lambda,先不管Java內部是怎麼實現lambda的,至少從形式上看是很像直接傳了個函式進去的。那麼,繼續用這個點選作為例項:

class SecondActivity {
   fun clickEvent(click: () -> Unit){
       println("click event start")
       click()
   }
}
​
fun main(args: Array<String>) {
   val secondActivity = SecondActivity()
   secondActivity.clickEvent({
       println("do something about click event")
   })
}
// 輸出:
// click event start
// do something about click event
複製程式碼

首先解釋一下clickEvent函式,click: () 表示函式型別, -> Unit表示返回值為空,也就是clickEvent接收一個返回值為空的click()方法。在clickEvent方法內部呼叫了這個傳入的方法,達到了傳遞事件的目的。當然,如果函式是作為最後一個引數,是可以寫成這樣的:

secondActivity.clickEvent() {
    println("do something about click event")
}
複製程式碼

如果沒有其他引數,那麼還能寫成這樣:

secondActivity.clickEvent {
    println("do something about click event")
}
複製程式碼

相比Java,黑科技的味道慢慢的就出來了。光看這些可能你會覺得沒啥,那麼接下來的騷操作一定會讓你對Kotlin的感覺提升一截。我也是在貓哥給我指導了一番之後,十分驚訝,還有這種操作?

令人窒息的操作

利用高階函式可以接受函式引數這一點,能玩的東西可就多了去了,接下來就寫幾個好玩的:

debug模式配置

inline fun debugConf(code: () -> Unit) {
   if (BuildConfig.DEBUG) {
       code()
   }
}
​
   debugConf {
       // init
   }
複製程式碼

這只是一個比較簡單的應用場景,類似的還有Android中支援版本程式碼的編寫,每一次都需要判斷版本然後呼叫api,無疑是十分麻煩的,我們也可以利用高階函式的特性來搞定。具體的程式碼就不再編寫了。接下來舉一個利用擴充套件和高階函式特性編寫的一個判斷集合元素是否滿足條件的函式。我們利用擴充套件給List擴充套件一個叫做myAll的方法,List呼叫此方法判斷集合中的所有元素是否滿足條件,如果所有元素都滿足,返回true,反之false。

inline fun <T> List<T>.myAll(getItem: (T) -> Boolean): Boolean {
   for (element in this) {
       if (!getItem(element)) {
           return false
       }
   }
   return true
}
​
fun main(args: Array<String>) {
 val list = listOf(1, 2, 3, 4, 5, 6)
 println("myAll:" + list.myAll { it % 1 == 0 })
 println("myAll: " + list.myAll { it % 2 == 0 })

}
// 輸出:
// myAll:true
// myAll: false
複製程式碼

簡單的解釋一下,inline是行內函數,在編譯時編譯器將函式體嵌入在每一個呼叫處。先看最外層,一個泛型T,用來表示集合內元素型別。myAll方法返回Boolean型別,用來表示所有元素是否符合條件。而getItem方法接收型別為T的引數(List元素型別),返回一個Boolean值表示該元素是否符合條件。判斷也是簡單粗暴的,遍歷List如果有getItem()的返回值是false,那麼就返回false。

小結

我看Kotlin也就是斷斷續續的看了一些語法,總體看下來感覺Java開發者學習Kotlin的成本不是非常高,而且最重要的是,Kotlin相容Java,遷移成本也非常的低。Kotlin中的很多特性,只是提供了一種語言層面上的支援,Java也不是不能實現,總得拐彎抹角,麻煩的一批。Java語法囉嗦受人詬病也不是一天兩天了,不過Java發展到現在更像是一個平臺而不是語言了,Java能做的事太多,語法已經不是最值得關注的事情了。不過如果有一個相容Java而且還餵你各種語法糖的語言出現,為什麼不嘗試一下呢?如果我說成這樣你還不嘗試一下,那我只能……

強行給你安利一波了,大爺,試一下Kotlin唄~

參考資料

相關文章