一.多态的不同方式
对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添加方法或属性。
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核心编程