第七色在线视频,2021少妇久久久久久久久久,亚洲欧洲精品成人久久av18,亚洲国产精品特色大片观看完整版,孙宇晨将参加特朗普的晚宴

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 也是使用 abstractinterface 來分別聲明抽象類和接口,除此之外 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ù)類、枚舉類、密封類等。