step by step Kotlin实现ButterKnife

jopen 9年前


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

Calling Java code from Kotlin

来自: http://www.jianshu.com/p/a263500192b8