Kotlin的接口与扩展
接口
与Java类似,Kotlin使用interface关键字定义接口,同时允许方法有默认实现:
interface KtInterfaceTest {
fun method()
fun methodGo(){
println("上面方法未实现,此方法已实现")
}
}
接口实现
一个类或者对象可以实现一个或多个接口。
class Demo : KtInterfaceTest{
override fun method() {
println("在类Demo中实现KtInterfaceTest接口的method()")
}
}
我们在主函数中调用一下:
fun main(args: Array<String>) {
val demo = Demo()
demo.method()
demo.methodGo()
}
对应控制台输出为:
接口中的属性
接口中的属性只能是抽象的,不允许初始化值,接口不会保存属性值。在实现接口时,必须重写属性。
interface KtInterfaceTest {
var urlString : String //抽象属性,不允许实例化具体值
}
class Demo : KtInterfaceTest{
override var urlString: String = "www.google.com"
}
函数重写冲突
接口的重写跟类的重写类似,如果父类中声明了许多类型,有可能出现一个方法的多种实现,则必须重写这个成员并且提供自己的实现,而要使用父类中提供的方法,则需要用super<Base类>来表示。
interface A {
fun foo() { print("A") }
fun bar()
}
interface B {
fun foo() { print("B") }
fun bar() { print("bar") }
}
class C : A {
override fun bar() { print("bar") }
}
class D : A, B {
override fun foo() {
super<A>.foo()
super<B>.foo()
}
}
如上代码,接口A,B都声明了foo()、bar()函数,接口B两个方法都有实现,接口A只实现了foo(),而bar()在A接口中并没有声明是抽象函数,所以C类要重写并实现bar()。而D类,同时继承A,B两个接口,不用重写bar()方法,因为继承的B接口已经实现同名方法。但由于继承了两个接口的foo()实现,所以需要用super<base类>关键字来区分。
扩展
kotlin同C#和Gson类似,能够扩展一个类的新的属性或函数(方法)而无需继承该类,且无需使用装饰模式在内的任何类型的设计模式。扩展是一种静态行为,对被扩展的类代码本身不会造成任何影响。
扩展函数
扩展函数可以在已有类中添加新的方法,不会对原类做修改,扩展函数的定义形式:
fun receiverType.functionName(params){
body
}
· receiverType:表示函数扩展的对象(接收者)
· functionName:扩展函数的名称
· params:扩展函数的参数,可以为null
下面是一个对User类的扩展:
class User(var number:Int)
fun User.Printf(){
println(" 用户号 : $number")
}
fun main(args: Array<String>) {
var user = User(123)
user.Printf()
}
对应输出结果为:
同时,这个扩展函数还可改为:
fun User.Printf(){
println(" 用户号 : "+this.number)
}
运行结果也是一致的。这里要说明下,这里的this关键字指代的是函数扩展对象(receiver object)(也就是调用扩展函数时, 在点号之前指定的对象实例)。
扩展是静态解析的
扩展不能真正的修改他们的(扩展)类。扩展方法并没有在类中插入新的成员,仅仅是可以通过该类型的变量使用点的表达式调用这个函数。
扩展函数一直强调是静态解析的,并不是接受者类型的虚拟成员。在调用扩展函数的时候,具体被调用的是哪一个函数,这一点由调用函数的对象表达式来决定,而不是动态的类型来决定的,如下示例:
open class Fruit
class Apple: Fruit()
fun Apple.foo() = " is apple " // 扩展函数 foo
fun Fruit.foo() = " is fruit" // 扩展函数 foo
fun printFoo(f: Fruit) {
println(f.foo()) // 类型是 Fruit 类
}
fun main(arg:Array<String>){
printFoo(Apple())
}
对应的输出结果是:
若扩展函数和成员函数一致,则使用该函数时,会优先使用成员函数。
class Apple{
fun foo(){
println(" is apple in class ") // 成员函数 foo
}
}
fun Apple.foo() = println(" is apple in extend ") // 扩展函数 foo
fun main(arg:Array<String>){
var apple = Apple()
apple.foo()
}
对应的控制台输出结果为:
扩展一个空对象
在扩展函数内,可通过this关键字来判断接收者(receiverType)是否为null。这样,即使接收者为null,也可以调用扩展函数。如下:
fun Any?.toString(): String {
if (this == null) return "null"
// 空检测之后,“this”会自动转换为非空类型,所以下面的 toString()解析为 Any 类的函数toString()
return toString()
}
fun main(arg:Array<String>){
var demo = null
println(demo.toString())
}
对应的控制台输出结果为:
扩展属性
除了扩展函数,kotlin也支持扩展属性。扩展属性允许定义在类或者kotlin文件中,不允许定义在函数中。初始化属性因为属性没有后端字段(backing field),所以不允许被初始化,只能由显式提供的 getter/setter 定义。
val User.foo = 1 // 错误:扩展属性不能有初始化器
val <T> List<T>.lastIndex: Int
get() = size - 1 //正确
值得注意的是,扩展属性只能被声明为 val。
伴生对象的扩展
如果一个类有一个伴生对象 ,你也可以为伴生对象定义扩展函数和属性。伴生对象通过"类名."形式调用伴生对象,伴生对象声明的扩展函数,通过用类名限定符来调用:
class NormalClass {
companion object { } // 将被称为 "Companion"
}
fun NormalClass.Companion.foo() {
println(" 伴生对象Companion的扩展函数 ")
}
val NormalClass.Companion.no: Int
get() = 10
fun main(args: Array<String>) {
println(" no:${NormalClass.no} ")
NormalClass.foo()
}
在控制台对应的输出结果为:
补充:伴生对象内的成员相当于 Java 中的静态成员,其生命周期伴随类始终,在伴生对象内部可以定义变量和函数,这些变量和函数可以直接用类名引用。
对于伴生对象扩展函数,有两种形式,一种是在类内扩展,一种是在类外扩展,这两种形式扩展后的函数互不影响(甚至名称都可以相同),即使名称相同,它们也完全是两个不同的函数,并且有以下特点:
(1)类内扩展的伴随对象函数和类外扩展的伴随对象可以同名,它们是两个独立的函数,互不影响;
(2)当类内扩展的伴随对象函数和类外扩展的伴随对象同名时,类内的其它函数优先引用类内扩展的伴随对象函数,即对于类内其它成员函数来说,类内扩展屏蔽类外扩展;
(3)类内扩展的伴随对象函数只能被类内的函数引用,不能被类外的函数和伴随对象内的函数引用;
(4)类外扩展的伴随对象函数可以被伴随对象内的函数引用。
示例代码如下:
class RunProgram {
companion object {
val mFieldNum: Int = 1
var mFieldString = "this is mFieldString"
fun companionFun1() {
println("this is 1st companion function.")
foo()
}
fun companionFun2() {
println("this is 2st companion function.")
companionFun1()
}
}
fun RunProgram.Companion.foo() {
println("伴随对象的扩展函数(内部)")
}
fun test2() {
RunProgram.foo()
}
init {
test2()
}
}
val RunProgram.Companion.no: Int
get() = 10
fun RunProgram.Companion.foo() {
println("foo 伴随对象外部扩展函数")
}
fun main(args: Array<String>) {
println("no:${RunProgram.no}")
println("field1:${RunProgram.mFieldNum}")
println("field2:${RunProgram.mFieldString}")
RunProgram.foo()
RunProgram.companionFun2()
}
对应控制台输出结果为:
扩展的作用域
通常扩展函数或扩展属性就定义在顶级包下:
package ktfoo.program
fun Ktz.goo() { …… }
如果要使用所定义包之外的一个扩展, 可通过import导入扩展的函数名进行使用:
package com.example.demo
import ktfoo.program.goo // 导入所有名为 goo 的扩展
// 或者
import ktfoo.program.* // 从 ktfoo.program 导入一切
fun usage(baz: Ktz) {
baz.goo()
}
扩展声明为成员
在kotlin中,一个类内部你可以为另一个类声明扩展。在这个扩展中,有个多个隐含的接受者,其中扩展方法定义所在类的实例称为分发接受者,而扩展方法的目标类型的实例称为扩展接受者。
class Car {
fun bar() { println("Car bar") }
}
class Plane {
fun baz() { println("Plane baz") }
fun Car.foo() {
bar() // 调用 Car.bar
baz() // 调用 Plane.baz
}
fun caller(car: Car) {
car.foo() // 调用扩展函数
}
}
fun main(args: Array<String>) {
val Plane: Plane = Plane()
val car: Car = Car()
Plane.caller(car)
}
可以推算出控制台输出为:
在上段代码中, Plane 类内,创建了Car类的扩展。此时,Plane被成为分发接受者,而Car为扩展接受者。从上例中,可以清楚的看到,在扩展函数中,可以调用派发接收者的成员函数。
假如在调用某一个函数,而该函数在分发接受者和扩展接受者均存在,则以扩展接收者优先,要引用分发接收者的成员你可以使用限定的 this 语法。示例如下:
class Car {
fun bar() { println("Car bar") }
}
class Plane {
fun bar() { println("Plane bar") } //注意
fun Car.foo() {
bar() // 调用 Car.bar
this@Plane.bar() // 调用 Plane.baz
}
fun caller(car: Car) {
car.foo() // 调用扩展函数
}
}
fun main(args: Array<String>) {
val Plane: Plane = Plane()
val car: Car = Car()
Plane.caller(car)
}
对应的控制台输出:
值得一提的是,以成员的形式定义的扩展函数, 可以声明为 open , 而且可以在子类中覆盖。(也可以说,在这类扩展函数的派发过程中, 针对分发接受者是虚拟的(virtual), 但针对扩展接受者仍然是静态的。)
open class Car {
}
class SUV : Car(){
}
open class Plane {
open fun Car.drive() {
println(" Car driving in the Plane ")
}
open fun SUV.drive() {
println(" SUV driving in the Plane ")
}
fun caller(car: Car){
car.drive() //调用扩展函数
}
}
class FighterPlane : Plane(){
override fun Car.drive() {
println(" Car driving in the FighterPlane ")
}
override fun SUV.drive() {
println(" SUV driving in the FighterPlane ")
}
}
fun main(args: Array<String>) {
Plane().caller(Car())
FighterPlane().caller(Car()) //分发接收者虚拟解析
Plane().caller(SUV()) //扩展接收者静态解析
}
对应控制台输出为:
End,如有问题请留言讨论。