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