Kotlin多态和扩展总结

一.多态的不同方式

对Java而言,多态是面向对象设计的一个重要特征。当我们使用一个子类继承一个父类的时候,这就是子类型多态。另一种是参数多态(泛型)。

1.1子类型多态

class PayCustomerHelper(context: Context) : SQLiteOpenHelper(context, "kotlin.db", null, 1) {
    override fun onCreate(db: SQLiteDatabase?) {
        
    }

    override fun onUpgrade(db: SQLiteDatabase?, oldVersion: Int, newVersion: Int) {
    }
}

上面的方法中通过继承,可以使用父类SQLiteOpenHelper的方法,这就是子类型多态。

1.2参数多态

最常见的参数多态就是泛型。

class ClassA(override val uniqueKey: String) : PayKeyI {
}
class ClassB(override val uniqueKey: String) :PayKeyI {
}
fun <T : PayKeyI> persisit(t: T) {
    
}

参数多态在程序设计语言与类型论中是指 声明与定义函数、复合类型、变量时不指定其具体的类型,而把这部分类型作为参数使用,使得该定义对各种具体类型都适用,所以它建立在运行时的参数基础之上,并且所有这些都是在不影响类型安全的前提下进行的。

1.3对第三方类进行扩展‘

假如当对应的业务类ClassA、ClassB是第三方引入的,而且不可被修改,如果想要给它们扩展一些方法,比如将对象转化为Json。就可以使用Kotlin的扩展语法,给ClassA、ClassB添加方法或属性。

扫描二维码关注公众号,回复: 10044965 查看本文章

fun ClassA.toJson():String={

... ...

}

如上面代码,给ClassA类扩展了一个将对象转换为Json的toJson方法。需要注意的是,扩展属性和方法的实现运行在ClassA实例,它们的定义操作并不会修改ClassA类本身。这样被扩展的第三方类免于被污染,从而避免了一些因父类修改而可能导致子类出错的问题发生。

1.4特设多态与运算符重载

例子:

下面写法会报错:

fun <T> sum(x: T, y: T): T = x + y

因为某些类型T的实例不一定支持加法操作,而且对于一些自定义类,更希望能够实现各自定制化的“加法语义上的操作”。所以这个例子表现出参数类型多态存在的问题。

换一种思路:

定义一个通用接口,需要支持加法操作的类实现这个接口。

interface Sumable<T> {
    fun plusThat(that: T): T
}

data class Len(val v: Int) : Sumable<Len> {
    override fun plusThat(that: Len): Len = Len(this.v + that.v)
}

 通过上面代码发现:当自定义一个支持plusThat方法的数据结构如Len时,这种做法没有问题。但是,如果是针对不可修改的第三方类扩展加法操作时,这种通过子类型多态的技术手段也会遇到问题。

特设多态:一个多态函数有多个不同的实现,依赖于其实参而调用相应版本的函数。

Kotlin支持运算符重载:

data class Area(val value: Double)

operator fun Area.plus(that: Area): Area {
    return Area(this.value + that.value)
}

fun main() {
    println(Area(1.0) + Area(2.0))
}

Area(value=3.0)

 Kotlin中运算符重载:operator关键字,以及Kotlin中内置可重载的运算符plus。operator的作用:将一个函数标记为重载一个操作符或者实现一个约定。plus是Kotlin规定的函数名。除了重载加法,还可以重载减法(minus)、乘法(times)、除法(div)、取余(mod)(Kotlin1.1版本开始被rem替代)等函数实现重载运算符。

fun main() {
    val str = "abc"
    println("a" in str)
    println(str.contains("a"))
    payPrint(str)
    val payFun = ::payPrint
    payFun.invoke(str)
}

fun payPrint(str: String) {
    println(str)
}
true
true
abc
abc

二、扩展:为别等类添加方法或者属性

1.扩展与开放封闭原则

开放封闭原则概念:

开放、封闭原则是面向对象原则的核心。软件设计本身所追求的目标是封装变化、降低耦合,而开放封闭原则正是对这一目标最直接表现。

例子:比如在进行android开发的时候,为了实现某一个需求,需要引入一个第三方类库。但某一天,需求发生了变动,当前库无法满足。此时也许你只能开始尝试对库源码进行修改,这就违背了开放封闭原则。

Java中一种惯常的样应对方案是让第三方库类继承一个子类,然后添加新功能,但是强行继承可能违背“里氏替换原则”。

更为合理的方案是依靠扩展这个语言特性。Kotlin通过扩展一个类的新功能而无需继承该类。

2.使用扩展函数、属性

扩展函数的声明非常简单,它的关键字是<Type>。此外需要一个“接收者类型”(receiver type)(通常是类或者接口的名称)来作为它的前缀。

fun MutableList<Int>.exchange(fromIndex: Int, toIndex: Int) {
    val temp = this[fromIndex]
    this[fromIndex] = this[toIndex]
    this[toIndex] = temp
}

fun main() {
    val mutableListOf = mutableListOf<Int>(1, 2, 3)
    mutableListOf.exchange(1,2)
    println(mutableListOf)
}

[1, 3, 2]

 上面代码中,MutableList<T>是Kotlin标准库Collections中的List容器类,这里作为receiver type,exchange是扩展函数名。其余和Kotlin声明一个普通函数没有任何区别。Kotlin中的this要比 Java灵活,这里扩展函数体里的this代表的是接收者类型的对象。Kotlin严格区分接收者是否可空。如果你的函数是可空的,你需要重写一个可空类型的扩展函数。

3.扩展函数的实现机制

将Mutable<Int>.exchange的例子转换为对应的Java代码:

@Metadata(
   mv = {1, 1, 15},
   bv = {1, 0, 3},
   k = 2,
   d1 = {"\u0000\u0012\n\u0000\n\u0002\u0010\u0002\n\u0002\u0010!\n\u0002\u0010\b\n\u0002\b\u0003\u001a \u0010\u0000\u001a\u00020\u0001*\b\u0012\u0004\u0012\u00020\u00030\u00022\u0006\u0010\u0004\u001a\u00020\u00032\u0006\u0010\u0005\u001a\u00020\u0003¨\u0006\u0006"},
   d2 = {"exchange", "", "", "", "fromIndex", "toIndex", "app"}
)
public final class Test4Kt {
   public static final void exchange(@NotNull List $this$exchange, int fromIndex, int toIndex) {
      Intrinsics.checkParameterIsNotNull($this$exchange, "$this$exchange");
      int temp = ((Number)$this$exchange.get(fromIndex)).intValue();
      $this$exchange.set(fromIndex, $this$exchange.get(toIndex));
      $this$exchange.set(toIndex, temp);
   }
}

从上面的Java代码可以看出,可以将扩展函数近似理解为静态方法。Java静态方法的特点:它独立于该类的任何对象,且不依赖类的特定实例,被该类的所有实例共享。此外,被public修饰的静态方法本质上也就是全局方法。

所以:扩展函数不会带来额外的性能开销。

4.扩展函数的作用域

一般习惯于将扩展函数直接定义在包内,例如上面的exchange方法,将其放在com.example.kotlindemo.extension包下。

package com.example.kotlindemo.extension

fun MutableList<Int>.exchange(fromIndex: Int, toIndex: Int) {
    val temp = this[fromIndex]
    this[fromIndex] = this[toIndex]
    this[toIndex] = temp
}

在同一个包内是可以直接调用exchange方法的。如果在其他包内调用,只需要import相应的方法即可,这与调用Java全局静态方法类似。除此之外,实际开发时,我们也能将扩展函数定义在一个Class内部统一管理。

package com.example.kotlindemo.demo2
//不同包下,需要import
import com.example.kotlindemo.extension.exchange

fun main() {
    val mutableListOf = mutableListOf<Int>(1, 2, 3)
    mutableListOf.exchange(1,2)
    println(mutableListOf)
}

如果将扩展函数定义在类内部:

class Extends {

    fun MutableList<Int>.exchange(fromIndex: Int, toIndex: Int) {
        val temp = this[fromIndex]
        this[fromIndex] = this[toIndex]
        this[toIndex] = temp
    }
}

exchange方法在类Extends外部就无法调用了。

转Java代码观察原因:

@Metadata(
   mv = {1, 1, 15},
   bv = {1, 0, 3},
   k = 1,
   d1 = {"\u0000\u001c\n\u0002\u0018\u0002\n\u0002\u0010\u0000\n\u0002\b\u0002\n\u0002\u0010\u0002\n\u0002\u0010!\n\u0002\u0010\b\n\u0002\b\u0003\u0018\u00002\u00020\u0001B\u0005¢\u0006\u0002\u0010\u0002J \u0010\u0003\u001a\u00020\u0004*\b\u0012\u0004\u0012\u00020\u00060\u00052\u0006\u0010\u0007\u001a\u00020\u00062\u0006\u0010\b\u001a\u00020\u0006¨\u0006\t"},
   d2 = {"Lcom/example/kotlindemo/extension/Extends;", "", "()V", "exchange", "", "", "", "fromIndex", "toIndex", "app"}
)
public final class Extends {
   public final void exchange(@NotNull List $this$exchange, int fromIndex, int toIndex) {
      Intrinsics.checkParameterIsNotNull($this$exchange, "$this$exchange");
      int temp = ((Number)$this$exchange.get(fromIndex)).intValue();
      $this$exchange.set(fromIndex, $this$exchange.get(toIndex));
      $this$exchange.set(toIndex, temp);
   }
}

通过上面代码可以发现,exchange方法上面已经没有static关键字修饰了。所以当扩展方法在类内部时,我们只能在该类和该类的子类中进行调用。

5.扩展属性

例如给MutableList<Int>添加判断和是否是一个偶数的属性sumIsEven:

val MutableList<Int>.sumIsEven: Boolean
    get() = this.sum() % 2 == 0


fun main() {
    val mutableListOf = mutableListOf<Int>(1, 2, 3)
    println(mutableListOf.sumIsEven)
}

true

下面是错误写法:

当准备给扩展属性添加默认值时,会报错。

val MutableList<Int>.sumIsEven: Boolean = false//会报错
    get() = this.sum() % 2 == 0

 原因:与扩展函数一样,其本质也是对应Java中的静态方法(反编译Java代码可以看发现是个getSumIsEven的静态方法,与扩展函数类似)。由于扩展没有实际地将成员插入类中,因此对扩展属性来说幕后字段是无效的。它们的行为只能显示提供getter和setter定义。

幕后字段:

在Kotlin中,如果属性中存在访问器使用默认实现,那么Kotlin会自动提供幕后字段filed,其仅可以用于自定义getter和setter中。

@Metadata(
   mv = {1, 1, 15},
   bv = {1, 0, 3},
   k = 2,
   d1 = {"\u0000\u0012\n\u0000\n\u0002\u0010\u000b\n\u0002\u0010!\n\u0002\u0010\b\n\u0002\b\u0003\"\u001b\u0010\u0000\u001a\u00020\u0001*\b\u0012\u0004\u0012\u00020\u00030\u00028F¢\u0006\u0006\u001a\u0004\b\u0004\u0010\u0005¨\u0006\u0006"},
   d2 = {"sumIsEven", "", "", "", "getSumIsEven", "(Ljava/util/List;)Z", "app"}
)
public final class Test2Kt {
   public static final boolean getSumIsEven(@NotNull List $this$sumIsEven) {
      Intrinsics.checkParameterIsNotNull($this$sumIsEven, "$this$sumIsEven");
      return CollectionsKt.sumOfInt((Iterable)$this$sumIsEven) % 2 == 0;
   }
}

6.扩展的特殊情况

6.1类似Java的静态扩展函数

在Kotlin中,如果需要声明一个静态的扩展函数,就必须定义在伴生对象(companion object)上。所以此时我们需要定义带有伴生对象的类:

class Son {
    companion object {
        val age = 10
    }
}

Son类中已经有一个伴生对象,如果我们现在不希望在Son中定义扩展函数,而是定义在Son的伴生对象上:

fun Son.Companion.payFoo() {
    println("age=$age")
}

这时,即使Son没有实例对象的情况下,也能调用到这个扩展函数,调用语法类似Java到静态方法:

fun main() {
    Son.payFoo()
}

age=10

 但是如果想让第三方库也支持这种写法时,会发现第三方库中的类不都存在伴生对象。所以这时只能通过它的实例来进行调用。

6.2成员方法优先级总高于扩展函数

如果扩展函数和成员函数的方法名相同,哪一个优先级更高呢?

class PaySon{
    fun payFoo() = println("son called member payFoo")
}

fun PaySon.payFoo()= println("son called extention payFoo")

fun main() {
    PaySon().payFoo()
}

son called member payFoo

 从上面的代码中可以发现:当扩展函数和现有类的成员方法同时存在时,Kotlin会默认使用类的成员方法。所以开发时需要注意:同名的类成员方法的优先级总高于扩展函数。

6.3类的实例和接收者实例

因为Kotlin中的this比Java中的灵活,以扩展函数为例,当在扩展函数里调用 this时,指代的是接收者类型的实例。那么如果这个扩展函数声明在一个object内部,此时如何通过this来获取到类的实例呢?

其实我们可以使用this@类名 来强行制定调用的this。

class PaySon {
    fun payFoo() = println("son called member payFoo")
}


object Parent {
    fun payFoo() = println("payFoo in Class Parent")

    @JvmStatic
    fun main(args: Array<String>) {
        fun PaySon.payFoo2() {
            this.payFoo()
            [email protected]()
        }
        PaySon().payFoo2()
    }
}

son called member payFoo
payFoo in Class Parent

 另外,如果PaySon扩展函数在Parent类内,我们将无法调用。即使此时我们设置访问权限为public,它也只能在该类或者该类的子类中被访问,如果我们设置访问权限为private,那么子类中也不能访问这个扩展函数。

class PaySonD {
    fun payFoo() {
        println("payFoo in Class PaySonD")
    }
}

class PayParent {
    fun payFoo() {
        println("payFoo in Class PayParent")
    }

    fun PaySonD.payFoo2() {
        this.payFoo()
        [email protected]()
    }
}

fun main() {
    PaySonD().payFoo()
}

payFoo in Class PaySonD


//下面的写法是不可访问的
fun main() {
    PaySonD().payFoo2()
}

将 PayParent代码转换为对应Java代码。

public final class PayParent {
   public final void payFoo() {
      String var1 = "payFoo in Class PayParent";
      System.out.println(var1);
   }

   public final void payFoo2(@NotNull PaySonD $this$payFoo2) {
      Intrinsics.checkParameterIsNotNull($this$payFoo2, "$this$payFoo2");
      $this$payFoo2.payFoo();
      this.payFoo();
   }
}


public final class PaySonD {
   public final void payFoo() {
      String var1 = "payFoo in Class PaySonD";
      System.out.println(var1);
   }
}

参考Kotlin核心编程 

发布了182 篇原创文章 · 获赞 179 · 访问量 12万+

猜你喜欢

转载自blog.csdn.net/zhangying1994/article/details/104861367