step by step Kotlin實現ButterKnife
欲善其事,必利其器。想要花式的使用一門語言,驚呆小伙伴,必須從實踐入手。知易行難,本文會帶領大家一步步的實現Kotlin版本的ButterKnife,深入的體會下Kotlin的魅力。
屬性
Kotlin的屬性是通過getter和setter來進行訪問。完整的屬性聲明方式為
var <propertyName>: <PropertyType> [ = <property_initializer> ] <getter> <setter>
但一般情況下,getter和setter是可以省略。值得注意的是,一定不要在getter和setter方法里操作自己,這會造成死循環的~ 為了避免這種情況的發生,Kotlin提供field關鍵字用于屬性的訪問器,可以直接使用field來訪問自身
var NotNullSingleString : String? = null get(){ return field ?: throw IllegalStateException("not initialized") } set(value) { if(field == null) field = value else throw IllegalStateException("already initialized") }
而這樣設計的好處在于,getter和setter方法很方便的被定義,可以更直接的對屬性進行包裝。當然,OO思想深入人心的今天,不抽象都不好意思說學過面向對象。欣慰的是,對此Kotlin提供一套更完善,優雅的解決方案——代理屬性
代理屬性
val view : TextView by Bind(R.id.text) class Bind<V>(val id: Int) : ReadOnlyProperty<Activity, V> { override fun getValue(thisRef: Activity, property: KProperty<*>): V { return thisRef.findViewById(id) as V } }
當然ReadOnlyProperty有一個兄弟接口ReadWriteProperty,還需要實現setValue(thisRef: R, property: KProperty<*>, value: T)
。這兩個接口分別對應val和var關鍵字。KProperty是屬性的metadata。
細心的小伙伴們應該發現了上面代碼問題,每次調用view的時候都需要去findViewById,囧。Kotlin提供了Lazy代理來解決這個問題。Lazy代理,提供的是一個線程安全的getter代理,在屬性第一次被調用時,Lazy的Lambdas會被執行,來給屬性進行初始化,在之后調用時,此屬性將不再被初始化。
val view : TextView by lazy { findViewById(R.id.text) as TextView }
提到Lambdas,這個眾多語言的語法糖,被廣大程序猿喜愛。下面我們就來看一下Kotlin的Lamdas和函數。
函數和Lambdas
函數在Kotlin中尤為重要,也是Kotlin的重要特色之一,我們先來嘗點甜頭
fun add(a : Int, b : Int) : Int { return a + b } fun add(a : Int, b : Int) = a + b
這個兩個函數是完全一樣的,我們可以很方便的省略掉返回值類型和括號。當然這還不夠,我們可以傳入一個Lambdas
fun compose<A, B, C>( f: (B) -> C, g: (A) -> B ): (A) -> C { return {x -> f(g(x))} } val oddLength = compose<String, Int, Boolean>({ it % 2 === 0 },{ it.length })
上面的函數實現了一個函數組合的功能。f : (B) -> C
表示名字為f,只有一個類型為B的參數,返回C。當Lambdas只有一個參數時,可用it代替。當然,有些時候我們不必要傳入這么多函數只需要一個,比如集合操作
var list = listOf(1, 2, 3) list.filter { it % 2 !== 0 }.map { "${it}是一個質素" }
作為動態語言,Kotlin同樣支持擴展,回到我們的ButterKnife。上面通過 by lazy
的實現方式顯然達不到我們想要的效果。同時Lazy持有一個internal constructor(),我們無法直接繼承,那么擴展是一個很好方式
public fun <V : View> Activity.bview(id : Int) : Lazy<V>{ return lazy { this.findViewById(id) as V } }
注意:在我們想給一個類添加全局擴展時,擴展方法要放在類的外部,并聲明為public
我們可以進一步抽象,給Activiy擴展一個新的屬性
private val Activity.viewFinder: Activity.(Int) -> View get() = { findViewById(it) } public fun <V : View> Activity.bview(id : Int) : Lazy<V>{ return lazy { this.viewFinder(id) as V } }
實際上,擴展是靜態加載的,它并沒有直接去修改類本身,也沒有給類直接添加一個成員,只是能讓這個類的實例通過 . 的方式訪問。
Lambdas作為java8的新特性,被眾多java的使用者所知曉,但對還在使用java6的android來說,只能望梅止渴。雖然有開源的解決方案,但在生產環境中使用依舊存在風險。不過openJDK也有可能帶來很多新的特性,其中有可能就包含Lambdas。我們先行感受下。
空安全
通過上面的事例,我們大體上實現了ButterKnife的功能,接下來我們進一步對其進行完善。
optional
首先我們要明確一點,可空類型和普通類型是完全不同。如果一定要聲明這個類型可空,直接使用?
val v: View = TextView(context) var v2: View? = null v.visibility = vi2?.visibility ?: View.GONE
?:
在Kotlin中被稱作Elvis operator。當然你也可以使用!!
來聲明類型非空。同時,非空代理也是個不錯的選擇,甚至可以說更優秀,他在初始化之前調用都會報錯。
var view : View by Delegates.notNull<View>()
之前,我們給父類添加了一個Lambdas屬性viewFinder。但是,他的返回值View是可能為空的。在這里我們需要對其進行處理
public fun <V : View> Activity.bindOptionalView(id: Int) : ReadOnlyProperty<Activity, V?> = optional(id, viewFinder) @Suppress("UNCHECKED_CAST") private fun <T, V : View> optional(id: Int, finder: T.(Int) -> View?) = Lazy { t: T, desc -> t.finder(id) as V? } private val Activity.viewFinder: Activity.(Int) -> View? get() = { findViewById(it) } private class Lazy<T, V>(private val initializer: (T, KProperty<*>) -> V) : ReadOnlyProperty<T, V> { private object EMPTY private var value: Any? = EMPTY override fun getValue(thisRef: T, property: KProperty<*>): V { if (value == EMPTY) { value = initializer(thisRef, property) } @Suppress("UNCHECKED_CAST") return value as V } }
我們重新聲明一個lazy代理來處理。并在view為空的時候什么也不做
致此我們基本完成了一個Kotlin版本的ButterKnife,Jake Wharton的Kotterknife
更多令人興奮的東西
接口
Kotlin中接口的方法是可以實現的,如同java8中的接口默認方法。但屬性是不能初始化的,必須在繼承的類中對其初始化。
這給我們提供了一種新的思路去定義接口,只提供屬性,不提供方法。比如KClass。
Kotlin的反射不同于java,它提供兩種機制,View::class
-> KClass View::class.java
-> Class
Data Object
Data Classes不同于其他對象,他的聲明尤其簡單
data class User(var name : String, var age : Int)
通過這種方式,就可以簡單的聲明一個pojo。同時Kotlin在Java注解方面的兼容非常出色,你可以直接使用Library的注解,例如Gson
data class User(@SerializedName("_id") var id : String, var name : String, var age : Int)
單例
Kotlin提供companion object,伴隨對象。在他內部聲明的屬性和方法可直接通過.的方式訪問。但不同于java的靜態塊,他也是個對象,可以繼承接口。并在反射中可以通過KClass.companionObjectInstance得到他的實例
class App : Application() { companion object { var instance: App by NotNullSingleValueVar() } override fun onCreate() { super.onCreate() instance = this } } private class NotNullSingleValueVar<T>() : ReadWriteProperty<Any?, T> { private var value: T? = null override fun getValue(thisRef: Any?, property: KProperty<*>): T { return value ?: throw IllegalStateException("${desc.name} " + "not initialized") } override fun setValue(thisRef: Any?, property: KProperty<*>, value: T) { this.value = if (this.value == null) value else throw IllegalStateException("${desc.name} already initialized") } }
擴展的域
我們可以直接引入某些方法,并在類中進行調用,這就省去了寫Utility的麻煩
import foo.bar.goo // 導入所有名字叫 "goo" 的擴展 // 或者 import foo.bar.* // 導入foo.bar包下得所有數據 fun usage(baz: Baz) { baz.goo() }
當然,Kotlin還有很多新奇好玩特性,同時通過實踐,最直觀的感受就是,他更像一個java的擴展版,一切都以java為基礎,去延伸而不是修改,去添加而不是破壞。