Kotlin 抽象與接口
上篇文章我們一起進(jìn)入了 Kotlin 面向?qū)ο蟮氖澜?,從這篇文章開(kāi)始將繼續(xù)探討 Kotlin 面向?qū)ο笾械睦^承與接口。其實(shí)在 Kotlin 中繼承、接口大部分和 Java 是一樣的,但是在語(yǔ)法層面支持是不一樣。因?yàn)?Kotlin 會(huì)有一層語(yǔ)法糖可以很方便高效地聲明某個(gè)語(yǔ)法,從而讓你把更多精力專(zhuān)注在業(yè)務(wù)邏輯上,而不是語(yǔ)法代碼模板上。然后我們還會(huì)一起來(lái)聊下 Kotlin 多繼承的實(shí)現(xiàn),Kotlin 和 Java 一樣都是單繼承,這一點(diǎn)是毋庸置疑的,但是我們也會(huì)需要多繼承場(chǎng)景,那么 Kotlin 是怎么解決這樣場(chǎng)景的呢?大家肯定想到的是接口多繼承,具體怎么一起來(lái)看看吧。
1. 抽象與接口
與 Java 一樣的是 Kotlin 也是使用 abstract
和 interface
來(lái)分別聲明抽象類(lèi)和接口,除此之外 Kotlin 的接口內(nèi)部還支持非抽象方法的實(shí)現(xiàn) (這一點(diǎn)和 Java8 中 default 方法很類(lèi)似),但是需要注意內(nèi)部不能包含任何的狀態(tài) (純函數(shù)的形式)。
1.1 抽象類(lèi)聲明
在 Kotlin 中抽象類(lèi)的聲明使用 abstract
關(guān)鍵字,抽象類(lèi)中方法使用 abstract
聲明抽象方法。
//以Random.kt源碼為例
public abstract class Random {//使用abstract關(guān)鍵聲明一個(gè)抽象類(lèi)Random
public abstract fun nextBits(bitCount: Int): Int //與Java一樣使用abstract聲明一個(gè)抽象類(lèi)中抽象方法,所以子類(lèi)必須要實(shí)現(xiàn)該方法
public open fun nextInt(): Int = nextBits(32)//open表示這個(gè)類(lèi)可以被子類(lèi)重寫(xiě)
public fun nextInt(until: Int): Int = nextInt(0, until)//由于Kotlin默認(rèn)是final且沒(méi)有顯式open,所以該方法不能被子類(lèi)重寫(xiě)
...
}
1.2 接口聲明
在 Kotlin 中接口的聲明使用 interface
關(guān)鍵字:
interface OnClickListener {//使用interface關(guān)鍵字聲明一個(gè)接口
fun onClick() //聲明了一個(gè)接口抽象方法,所有實(shí)現(xiàn)這個(gè)接口的非抽象類(lèi)都需要實(shí)現(xiàn)這個(gè)方法
}
在 Kotlin 中實(shí)現(xiàn)一個(gè)簡(jiǎn)單的接口:
class Button: OnClickListener {
override fun onClick() = println("Button is Clicked") //與Java不同的是在Kotlin中override必須是強(qiáng)制要求的
}
2. Kotlin 中帶默認(rèn)方法的接口
我們都知道在 Java8 以下版本中,接口中不能存在帶實(shí)現(xiàn)方法的。直到 Java8 出現(xiàn) default
方法,那么在 Java8 中可以聲明帶實(shí)現(xiàn)的方法。
//java8實(shí)現(xiàn)
public interface OnClickListener {
public void onClick();
default public void onClickLog() {//使用default關(guān)鍵字聲明接口中一個(gè)帶實(shí)現(xiàn)的方法,這個(gè)Java8以下版本是無(wú)法做到
System.out.println("clicked!");
}
}
我們看了 Java8 中是如何實(shí)現(xiàn)帶默認(rèn)方法的接口的,那么在 Kotlin 中是如何做到的呢?其實(shí)在 Kotlin 語(yǔ)法中天然就支持帶默認(rèn)實(shí)現(xiàn)的方法,不需要添加任何的關(guān)鍵字或修飾符。
//kotlin實(shí)現(xiàn)
interface OnClickListener {
fun onClick()
fun onClickLog() = println("clicked!")//不需要聲明任何關(guān)鍵字,直接支持帶默認(rèn)實(shí)現(xiàn)的方法
}
那么問(wèn)題就來(lái)了,我們都知道 Kotlin 是完全兼容到 Java6 的,然而 Java8 以下是不支持這種帶默認(rèn)實(shí)現(xiàn)的接口方法,那么 Kotlin 它是怎么做到 Java8 以下版本完全兼容這種語(yǔ)法特性呢?一起來(lái)反編譯它的 Kotlin 代碼就一目了然了。
//反編譯后java代碼
public interface OnClickListener {
void onClick();
void onClickLog();
@Metadata(
mv = {1, 1, 16},
bv = {1, 0, 3},
k = 3
)
public static final class DefaultImpls {//可以看到這邊自動(dòng)生成一個(gè)DefaultImpls靜態(tài)類(lèi)
public static void onClickLog(OnClickListener $this) {//默認(rèn)實(shí)現(xiàn)方法onClickLog被聲明成一個(gè)靜態(tài)方法
String var1 = "Clicked!";
boolean var2 = false;
System.out.println(var1);
}
}
}
可能你看到上面代碼還是不是很直觀,不知道它是如何觸發(fā) DefaultImpls
調(diào)用的,所以我們可以把上面代碼添加一個(gè)實(shí)現(xiàn)類(lèi),就能看到如何調(diào)用的了。
package com.imooc.test
interface OnClickListener {
fun onClick()
fun onClickLog() = println("Clicked!")
}
class Button : OnClickListener {//Button實(shí)現(xiàn)類(lèi)
override fun onClickLog() {//重寫(xiě)onClickLog方法
super.onClickLog()//默認(rèn)通過(guò)super調(diào)用父類(lèi)默認(rèn)實(shí)現(xiàn)的方法
}
override fun onClick() {
}
}
反編譯后的 Java 代碼:
// OnClickListener.java
public interface OnClickListener {
void onClick();
void onClickLog();
@Metadata(
mv = {1, 1, 16},
bv = {1, 0, 3},
k = 3
)
public static final class DefaultImpls {
public static void onClickLog(OnClickListener $this) {
String var1 = "Clicked!";
boolean var2 = false;
System.out.println(var1);
}
}
}
// Button.java
public final class Button implements OnClickListener {
public void onClickLog() {
OnClickListener.DefaultImpls.onClickLog(this);//現(xiàn)在可以看到實(shí)際上通過(guò)接口類(lèi)名調(diào)用它內(nèi)部靜態(tài)類(lèi)DefaultImpls,再通過(guò)靜態(tài)類(lèi)DefaultImpls調(diào)用它的靜態(tài)方法onClickLog
}
public void onClick() {
}
}
所以總結(jié)一下,Kotlin 中接口默認(rèn)實(shí)現(xiàn)方法是如何兼容 Java8 以下版本的,它實(shí)際上就是在接口內(nèi)部生成了一個(gè)靜態(tài)類(lèi) DefaultImpls
,并在靜態(tài)類(lèi)內(nèi)部生成對(duì)應(yīng)默認(rèn)實(shí)現(xiàn)靜態(tài)方法。然后調(diào)用的時(shí)候只需要通過(guò)接口名。靜態(tài)類(lèi) DefaultImpls
. 默認(rèn)實(shí)現(xiàn)靜態(tài)方法名調(diào)用即可。
3. Kotlin 中接口的多繼承
我們都知道在 Java 中是不支持多繼承的,然而 Kotlin 也一樣不支持類(lèi)的多繼承??墒菫槭裁匆@么設(shè)計(jì)呢,但是相信很多小伙伴應(yīng)該有過(guò)這樣的感受平時(shí)開(kāi)發(fā)中依然遇到類(lèi)似多繼承的場(chǎng)景。
3.1 為什么不支持類(lèi)的多繼承
我相信大家都知道經(jīng)典的多繼承問(wèn)題,俗稱(chēng) “鉆石繼承問(wèn)題”。我們用反證法,假設(shè) Java/Kotlin 中支持類(lèi)的多繼承,一起來(lái)看個(gè)例子,對(duì)于 A 類(lèi)中有一個(gè) invoke 方法,B,C 兩個(gè)類(lèi)都去繼承 A 類(lèi),然后 D 類(lèi)去分別去繼承 B,C 類(lèi)。
abstract class A {
abstract fun invoke()
}
class B: A {
override fun invoke() = println("B invoke")
}
class C: A {
override fun invoke() = println("C invoke")
}
class D: B,C {//假設(shè)支持類(lèi)的多繼承
override fun invoke() = println("C invoke")// B ? C
}
那么問(wèn)題就來(lái)了 D 類(lèi)應(yīng)該是繼承 B 類(lèi) invoke 方法,還是 C 類(lèi) invoke 方法呢?所以這樣類(lèi)的多繼承很容易帶來(lái)歧義。
但是我們知道在開(kāi)發(fā)過(guò)程中還是可能遇到多繼承的問(wèn)題,我們一般常用的方法是采用接口多繼承方式來(lái)解決,因?yàn)槲覀冎涝?Java 和 Kotlin 中是支持接口的多繼承的。
3.2 Kotlin 中接口的多繼承
在 Java 中接口多繼承是支持的,Kotlin 依然也支持。那么一起來(lái)看下在 Kotlin 對(duì)于上述多繼承問(wèn)題是如何解決的呢?
package com.imooc.test
interface A {
fun invoke()
}
interface B : A {
override fun invoke() {
println("B invoke")
}
}
interface C : A {
override fun invoke() {
println("C invoke")
}
}
class D : B, C {
//override fun invoke() = super<B>.invoke()//通過(guò)super中泛型類(lèi)型指定繼承B接口的方法,所以最后輸出"B invoke"
override fun invoke() = super<C>.invoke()//通過(guò)super中泛型類(lèi)型指定繼承C接口的方法,所以最后輸出"C invoke"
}
fun main() {
val d = D()
d.invoke()
}
4. 總結(jié)
到這里有關(guān) Kotlin 中抽象與接口就結(jié)束,其實(shí) Kotlin 中抽象和接口與 Java 中基本是相似的,只需要注意文章提到那幾點(diǎn)不一樣地方即可。多多對(duì)比多多體會(huì),下一篇文章我們將繼續(xù)探討 Kotlin 面向?qū)ο笾幸恍┍容^特殊的類(lèi),比如數(shù)據(jù)類(lèi)、枚舉類(lèi)、密封類(lèi)等。