1.概念
kotlin支持在不修改类代码的情况下,动态为类添加属性(扩展属性)和方法(扩展方法)。
2.扩展方法
扩展方法执行静态解析(编译时),成员方法执行动态解析(运行时)。
(1)语法格式
定义一个函数,在被定义的函数前面添加“类名.”,该函数即为该类名对应类的拓展方法。
fun main(args: Array<String>) {
val extensionClass = ExtensionClass()
//调用拓展方法
extensionClass.test()
}
//定义一个空类
class ExtensionClass
//为该空类定义一个拓展方法test()方法
fun ExtensionClass.test() = println("我是ExtensionClass的拓展方法")
(2)成员方法优先
如果被扩展的类的扩展方法与该类的成员方法名字和参数一样,该类对象调用该方法时,调用的会是成员方法。
fun main(args: Array<String>) {
val extension = ExtensionTest()
//此处调用的会是成员方法
extension.test()
}
class ExtensionTest {
fun test() = print("成员方法")
}
//该方法不会被调用
fun ExtensionTest.test() = println("扩展方法")
(3)为系统类添加拓展方法(以String为例)
fun main(args: Array<String>) {
val str = "123456"
//调用String的拓展方法
println(str.lastIndex())
}
//为String定义一个拓展方法
fun String.lastIndex() = length - 1
(4)扩展实现原理
java是一门静态语言,无法动态的为类添加方法、属性,除非修改类的源码,并重新编译该类。
kotlin扩展属性、方法时看起来是为该类动态添加了成员,实际上并没有真正修改这个被扩展的类,kotlin实质是定义了一个函数,当被扩展的类的对象调用扩展方法时,kotlin会执行静态解析,将调用扩展函数静态解析为函数调用。
静态解析:根据调用对象、方法名找到拓展函数,转换为函数调用。
如(2)str.lastIndex()方法执行的过程为:
①检查str类型(发现为String类型);
②检查String是否定义了lastIndex()成员方法,如果定义了,编译直接通过;
③如果String没定义lastIndex()方法,kotlin开始查找程序是否有为String类扩展了lastIndex()方法(即是否有fun String.lastIndex()),如果有定义该扩展方法,会执行该扩展方法;
④既没定义lastIndex()成员方法也没定义扩展方法,编译自然不通过。
(5)静态解析调用扩展方法注意点
由于静态调用扩展方法是在编译时执行,因此,如果父类和子类都扩展了同名的一个扩展方法,引用类型均为父类的情况下,会调用父类的扩展方法。
/**
* 拓展属性、方法
*/
fun main(args: Array<String>) {
val father : ExtensionTest = ExtensionTest()
father.test()//调用父类扩展方法
val child1 : ExtensionTest = ExtensionSubTest()
child1.test()//引用类型为父类类型,编译时静态调用的还是父类的扩展方法
val child2 : ExtensionSubTest = ExtensionSubTest()
child2.test()//此时才是调用子类的扩展方法
}
/**
* 父类
*/
open class ExtensionTest
/**
* 子类
*/
class ExtensionSubTest : ExtensionTest()
/**
* 父类扩展一个test方法
*/
fun ExtensionTest.test() = println("父类扩展方法")
/**
* 子类扩展一个test方法
*/
fun ExtensionSubTest.test() = println("子类扩展方法")
(6)可空类型扩展方法(以扩展equals方法为例)
kotlin允许扩展可空类型扩展方法,这样,null也能调用该方法。
fun main(args: Array<String>) {
val a: Any? = null
val b: Any? = null
println(a.equals(b))
}
fun Any?.equals(any: Any?): Boolean = this != null && any != null && any.equals(this)
3.扩展属性
(1)概念
kotlin允许动态为类扩展属性,扩展属性是通过添加get、set方法实现,没有幕后字段(filed)。
扩展属性也没有真的为该类添加了属性,只能说是为该类通过get、set方法计算出属性。
限制:①扩展属性不能有初始值;②扩展属性不能用filed关键字访问幕后字段;③val必须提供get方法,var必须提供get和set方法。
(2)定义扩展属性
fun main(args: Array<String>) {
val extensionTest = ExtensionTest("a", "b")
println(extensionTest.param1)//a
println(extensionTest.param2)//b
println(extensionTest.extensionParam)//a-b
}
/**
* 定义一个类,包含属性param1、属性param2
*/
class ExtensionTest(var param1: String, var param2: String)
/**
* 为该类扩展属性extensionParam
*/
var ExtensionTest.extensionParam: String
set(value) {
param1 = "param1$value"
param1 = "param2$value"
}
get() = "$param1-$param2"
4.以类成员方式定义扩展
在某个类里面为其他类定义扩展方法、属性,该扩展的方法,只能在该类中通过被扩展的类的对象调用扩展方法。
以类成员方式定义的扩展,属于被扩展的类,因此在扩展方法直接调用被扩展的类的成员(this可以省略),同时因为它位于所在类中,因此又可以直接调用所在类的成员。
fun main(args: Array<String>) {
val extensionTest = ExtensionTest()
val extensionTest2 = ExtensionTest2()
extensionTest2.info(extensionTest)
}
/**
* 定义一个类包含test方法
*/
class ExtensionTest {
fun test() = println("ExtensionTest的test方法")
}
/**
* 定义一个类包含test方法,包含ExtensionTest的一个扩展方法
*/
class ExtensionTest2 {
val a = "a"
fun test() = println("ExtensionTest2的test方法")
fun ExtensionTest.func() {
println(a)//调用扩展类的成员
test()//调用被扩展类的成员,相当于this.test()
this@ExtensionTest2.test()//同名的需要用this@类名的方式来调用
}
fun info(extensionTest: ExtensionTest) {
extensionTest.func()
}
}
5.带接收者的匿名扩展函数
(1)概念
扩展方法(fun 类名.方法名())去掉方法名就是所谓的带接收者的匿名扩展函数,接收者就是类本身,形如:fun Int.() : Int。
fun main(args: Array<String>) {
val extensionTest = ExtensionTest()
println(extensionTest.noNameExtensionFun("向带接收者的匿名函数传入的参数"))//使用匿名扩展函数
}
/**
* 定义一个空类
*/
class ExtensionTest
/**
* 为空类定义一个带接收者的匿名扩展函数
*/
var noNameExtensionFun = fun ExtensionTest.(param: String): String {
println(param)
return "我是来自带接收者的匿名扩展函数的返回值"
}
(2)带接收者的匿名扩展函数的函数类型
与普通函数一样,匿名扩展方法也有函数类型,(1)中的函数类型为:ExtensionTest.(String) -> String
(3)带接收者的匿名扩展函数与lambda表达式
如果能根据上下文推断出接收者类型,则可以使用lambda表达式
fun main(args: Array<String>) {
test {
println(it)
"匿名扩展函数返回值"
}
}
/**
* 定义一个空类
*/
class ExtensionTest
/**
* 定义一个函数,形参为ExtensionTest.(String) -> String类型,相当于同时为ExtensionTest类扩展了一个匿名扩展函数
*/
fun test(fn: ExtensionTest.(String) -> String) {
val extensionTest = ExtensionTest()
println("调用匿名扩展函数:${extensionTest.fn("匿名扩展函数传入形参")}")
}
6.扩展使用场景
扩展极大的增加了程序的灵活性,java如果想对一个类扩展某些属性,必须通过继承的方式等实现,kotlin使用语法直接可以动态的扩展,能更方便组织一些工具方法等。
fun main(args: Array<String>) {
"打印日志".log()
}
/**
* 为String字符串添加一个打印日志的扩展方法
*/
fun String.log() {
println(this)
}