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