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为基础,去延伸而不是修改,去添加而不是破坏。