开启一系列新文章记录Kotlin有关内容,基础语法部分就不过多赘述了,大家可以参见Kotlin官方白皮书,主要是记录一些个人对于该语音部分特性的理解。
首先,看一下官方对于扩展的定义:
能够扩展一个类的新功能而无需继承该类或使用像装饰者这样的任何类型的设计模式。 这通过叫做 扩展 的特殊声明完成。Kotlin 支持 扩展函数 和 扩展属性。
其实也不难理解,我们对比Java的特性分析一下:
Java语言中,类与类之间的功能是独立的,一个类能够获得其他类的属性或者功能时,需要通过继承来实现;或者持有其他类的实例对象从而获取它的功能(装饰者模式)等方式或手段来扩展一个有限功能的类。
而在Kotlin中,操作与实现将会变得更加简单方便,轻松的便可以对现有类进行拓展。
声明一个扩展函数,我们需要用一个 接收者类型 也就是被扩展的类型来作为他的前缀。
举个例子:
fun Context.toast(message: CharSequence, duration: Int = Toast.LENGTH_SHORT) {
Toast.makeText(this,message,duration).show()
}
这样一来,Context和任意它的子类都可以使用toast函数来完成弹窗。
这个 this 关键字在扩展函数内部对应到接收者对象(传过来的在点符号前的对象)。
同样的也能对泛型进行拓展:
fun MutableList.swap(index1: Int, index2: Int) {
val tmp = this[index1] // “this”对应该列表
this[index1] = this[index2]
this[index2] = tmp
}
val l = mutableListOf(1, 2, 3)
l.swap(0, 2) // “swap()”内部的“this”得到“l”的值
从上面的例子也能够看出,其实扩展并不是增加了类的方法,而是在扩展函数内部,讲被扩展类作为this参数传入,接受者将会使用这个this对象调用接受者的方法或者内部变量,从而实现了类功能的扩展。
open class C
class D: C()
fun C.foo() = "c"
fun D.foo() = "d"
fun printFoo(c: C) {
println(c.foo())
}
printFoo(D())
这里函数会输出 “c”,因为调用的扩展函数只取决于参数 c 的声明类型,该类型是 C 类。即使传入的是C的子类D对象,但是扩展函数的声明类型是C类。
当扩展函数与成员函数重名时:
class C {
fun foo() { println("member") }
}
fun C.foo() { println("extension") }
----------
class C {
fun foo() { println("member") }
}
fun C.foo(i: Int) { println("extension") }
上面会输出member,下面会输出extension。
即同名时以成员函数优先,参数不同时,互不影响。
fun Any?.toString(): String {
if (this == null) return "null"
// 空检测之后,“this”会自动转换为非空类型,所以下面的 toString()
// 解析为 Any 类的成员函数
return toString()
}
举个栗子:
class MyClass {
val studentList :ArrayList? = null
val teacherList : ArrayList? = null
.....
}
val MyClass.totalNum: Int
get() = studentList.size + teacherList.size
我们给MyClass类拓展了一个总人数的属性。
注意:由于扩展没有实际的将成员插入类中,因此对扩展属性来说幕后字段是无效的。这就是为什么扩展属性不能有初始化器。他们的行为只能由显式提供的 getters/setters 定义。
错误的例子:
val Foo.bar = 1 // 错误:扩展属性不能有初始化器
其实扩展属性和扩展函数有类似的点,那就是他们并不是真正插入到已有的类中新增加的方法或者属性,而是基于已有的类实例,获取并使用从而实现一系列新的操作或得到新的值。这样可以提高代码效率,减少很多模块代码的编写。
大多数时候我们在顶层定义扩展,即直接在包里:
package foo.bar
fun Baz.goo() { …… }
要使用所定义包之外的一个扩展,我们需要在调用方导入它:
package com.example.usage
import foo.bar.goo // 导入所有名为“goo”的扩展
// 或者
import foo.bar.* // 从“foo.bar”导入一切
fun usage(baz: Baz) {
baz.goo()
}
关于扩展这个特效,其目的是更高效的实现类方法和属性的扩展,而不去改变类本身原有的特性。并大量的减少模板代码的编写,提高效率。
使用扩展的动机,在官方文档上也给出了很好的解释:
在Java中,我们将类命名为“*Utils”:FileUtils、StringUtils 等,著名的 java.util.Collections 也属于同一种命名方式。 关于这些 Utils-类的不愉快的部分是代码写成这样:
// Java
Collections.swap(list, Collections.binarySearch(list, Collections.max(otherList)), Collections.max(list));
这些类名总是碍手碍脚的,我们可以通过静态导入达到这样效果:
// Java
swap(list, binarySearch(list, max(otherList)), max(list));
这会变得好一点,但是我们并没有从 IDE 强大的自动补全功能中得到帮助。如果能这样就更好了:
// Java
list.swap(list.binarySearch(otherList.max()), list.max());
但是我们不希望在 List 类内实现这些所有可能的方法,对吧?这时候扩展将会帮助我们。