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為基礎,去延伸而不是修改,去添加而不是破壞。