欲善其事,必利其器。想要花式的使用一门语言,惊呆小伙伴,必须从实践入手。知易行难,本文会带领大家一步步的实现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为基础,去延伸而不是修改,去添加而不是破坏。