与 C# 和 Gosu 类似, Kotlin也提供了一种,可以在不继承父类,也不使用类似装饰器这样的设计模式的情况下对指定类进行扩展。我们可以通过一种叫做扩展的特殊声明来实现他。
Kotlin 支持函数扩展和属性扩展。
扩展有什么好处?
其实很好理解。研发过程中经常会涉及到一些工具类,比如FileUtils用于操作文件,StringUtils用于操作字符串,Collecctions用于操作集合,然后在调用过程中就会显得很繁琐。比如这样:
// Java
Collections.swap(list, Collections.binarySearch(list,
Collections.max(otherList)), Collections.max(list))
静态导入包当然能简化:
// Java
swap(list, binarySearch(list, max(otherList)), max(list))
而我们实际上需要的是最好这样:
// Java
list.swap(list.binarySearch(otherList.max()), list.max())
所以在kotlin中考虑到这一点,就提供了扩展函数,可以对类的属性方法进行扩展,而又不用去改变类本身。
扩展函数
扩展函数可以在已有类中添加新的方法,不会对原类做修改,扩展函数定义形式:
fun receiverType.functionName(params){
body
}
- receiverType:表示函数的接收者,也就是函数扩展的对象
- functionName:扩展函数的名称
- params:扩展函数的参数,可以为NULL
以下是一个实例
class Functions constructor(var name:String) {
fun Functions.clean(){
this.name = ""
}
fun main(){
var func = Functions("name")
func.clean()
print(func.name)
}
}
this关键字指代接收者对象(receiver object)(也就是调用扩展函数时, 在点号之前指定的对象实例)。
特别地:
扩展函数是静态解析的,并不是接收者类型的虚拟成员,在调用扩展函数时,具体被调用的的是哪一个函数,由调用函数的的对象表达式来决定的,而不是动态的类型决定的:
open class C
class D: C()
fun C.foo() = "c" // 扩展函数 foo
fun D.foo() = "d" // 扩展函数 foo
fun printFoo(c: C) {
println(c.foo()) // 类型是 C 类
}
fun main(arg:Array<String>){
printFoo(D())
}
实例执行输出结果为:
c
若扩展函数和成员函数一致,则使用该函数时,会优先使用成员函数。
class C {
fun foo() { println("成员函数") }
}
fun C.foo() { println("扩展函数") }
fun main(arg:Array<String>){
var c = C()
c.foo()
}
实例执行输出结果为:
成员函数
扩展一个空对象
在扩展函数内, 可以通过 this 来判断接收者是否为 NULL,这样,即使接收者为 NULL,也可以调用扩展函数。例如:
fun Any?.toString(): String {
if (this == null) return "null"
// 空检测之后,“this”会自动转换为非空类型,所以下面的 toString()
// 解析为 Any 类的成员函数
return toString()
}
fun main(arg:Array<String>){
var t = null
println(t.toString())
}
实例执行输出结果为:
null
扩展属性
除了函数,Kotlin 也支持属性对属性进行扩展:
val <T> List<T>.lastIndex: Int
get() = size - 1
扩展属性允许定义在类或者kotlin文件中,不允许定义在函数中。初始化属性因为属性没有后端字段(backing field),所以不允许被初始化,只能由显式提供的 getter/setter 定义。
val Foo.bar = 1 // 错误:扩展属性不能有初始化器
Note:扩展属性只能被声明为 val。
扩展伴生对象
如果一个类定义有一个伴生对象 ,你也可以为伴生对象定义扩展函数和属性。
伴生对象通过"类名."形式调用伴生对象,伴生对象声明的扩展函数,通过用类名限定符来调用:
class MyClass {
companion object { } // 将被称为 "Companion"
}
fun MyClass.Companion.foo() {
println("伴随对象的扩展函数")
}
val MyClass.Companion.no: Int
get() = 10
fun main(args: Array<String>) {
println("no:${MyClass.no}")
MyClass.foo()
}
实例执行输出结果为:
no:10
伴随对象的扩展函数
Note:Kotlin中的伴生对象用于实现函数或属性的静态调用。后续的高级用法中会介绍。
扩展的作用域
我们知道对象或变量其实都有作用域的,Kotlin中扩展也不例外。
官方文档上的原文是这样
Most of the time we define extensions on the top level, i.e. directly under packages.
To use such an extension outside its declaring package, we need to import it at the call site
即官方的推荐是让我们在顶层包定义扩展,然后导包使用。但是,这好像是用法?其实我们更急切地是想知道:
- 是不是只能在该类中对该类进行扩展?
- 我在一个类中定义了某个类的扩展,在其它类想使用怎么弄?
首先,第一个问题————当然不是,不然这与类本身的新增函数有啥区别.kotlin中扩展的好处就是可以在无法修改或没有必要修改.kt类的情况下,直接通过该类调用扩展函数或属性,以达到代码上的简洁。
class Functions constructor(var name:String) {
fun Functions.clean(){
this.name = ""
}
fun Person.study2(){
print("start study")
}
}
以上代码不仅对Functions类进行函数扩展,也同样对Person进行了函数扩展。
第二个问题————通过实际代码总结了以下几点:
- 在某类中定义的扩展,无论是该类的扩展或是其它类的扩展,其作用域都仅限于该类以及该 类的内部类或子类中,在其它类或该类的嵌套类中都不可访问。
open class Functions constructor(var name:String) {
fun Functions.clean(){
this.name = ""
}
fun Person.study2(){
print("start study")
}
fun main(person: Person){
person.learn()
}
inner class A{//内部类中可以访问
fun main(){
var func = Functions("name")
func.clean();
val person = Person("name",18);
person.study2()
}
}
class N{
fun main(){
var func = Functions("name")
func.clean();//错误,嵌套类中无法访问
}
}
}
在子类中访问
class Func3(name: String) : Functions(name) {
fun main2(){
val func = Functions("anme");
func.clean()
val person = Person("name",18);
person.study2()
}
}
- 通用的扩展一般放在包的顶层,写在kotlin文件中(不是类,是文件)
Demo
创建ExtendMethods.kt文件(不是类)
package com.talent.kotlin.example
fun Person.learn(){
print("learn")
}
在Main中调用
package com.talent.kotlin.example.bbb
import com.talent.kotlin.example.Person
import com.talent.kotlin.example.learn
class Main {
fun main(){
val person = Person("name",18);
person.learn()
}
}
扩展声明为成员
首先先要弄清楚两个概念:
- 分发接受者
- 扩展接受者
在一个类内部你可以为另一个类声明扩展。在这个扩展中,有个多个隐含的接受者。而我们把:
扩展方法定义所在类的实例称为分发接受者
扩展方法的目标类型的实例称为扩展接受者
如下:
class ExtendsMembers {
fun buyApple(){
print("buy apple")
}
class Temp{
fun buyOrange(){
print("buy orange")
}
fun ExtendsMembers.buyMany(){
//在这里ExtendsMembers为扩展接受者
//Temp为分发接受者
buyApple()
buyOrange()
}
fun caller(members: ExtendsMembers) {
members.buyMany()//调用扩展函数
}
}
fun main(args: Array<String>){
val members = ExtendsMembers();
val temp = Temp();
temp.caller(members)
}
}
输出结果为:
buy apple
buy orange
从上例中,可以清楚的看到,在扩展函数中,可以调用派发接收者的成员函数。
当调用某一个函数,而该函数在分发接受者和扩展接受者均存在,则以扩展接收者优先,要引用分发接收者的成员你可以使用限定的 this 语法。
class D {
fun bar() { println("D bar") }
}
class C {
fun bar() { println("C bar") } // 与 D 类 的 bar 同名
fun D.foo() {
bar() // 调用 D.bar(),扩展接收者优先
this@C.bar() // 调用 C.bar()
}
fun caller(d: D) {
d.foo() // 调用扩展函数
}
}
fun main(args: Array<String>) {
val c: C = C()
val d: D = D()
c.caller(d)
}
实例执行输出结果为:
D bar
C bar
以成员的形式定义的扩展函数, 可以声明为 open , 而且可以在子类中覆盖. 也就是说, 在这类扩展函数的派 发过程中, 针对分发接受者是虚拟的(virtual), 但针对扩展接受者仍然是静态的。
open class D {
}
class D1 : D() {
}
open class C {
open fun D.foo() {
println("D.foo in C")
}
open fun D1.foo() {
println("D1.foo in C")
}
fun caller(d: D) {
d.foo() // 调用扩展函数
}
}
class C1 : C() {
override fun D.foo() {
println("D.foo in C1")
}
override fun D1.foo() {
println("D1.foo in C1")
}
}
fun main(args: Array<String>) {
C().caller(D()) // 输出 "D.foo in C"
C1().caller(D()) // 输出 "D.foo in C1" —— 分发接收者虚拟解析
C().caller(D1()) // 输出 "D.foo in C" —— 扩展接收者静态解析
}
即扩展的函数若在分发接收者的子类被重写,则调用分发接收者的子类方法,而扩展接收者则是依据方法的定义参数类型,是静态解析的。