淺談Kotlin語法篇之擴充套件函式(五)

mikyou發表於2018-04-16

簡述: 今天帶來的是Kotlin淺談系列的第五彈,這講主要是講利用Kotlin中的擴充套件函式特性讓我們的程式碼變得更加簡單和整潔。擴充套件函式是Kotlin語言中獨有的新特性,利用它可以減少很多的樣板程式碼,大大提高開發的效率;此外擴充套件函式的使用也是非常簡單的。我會從以下幾個方面闡述Kotlin中的擴充套件函式。

  • 1、為什麼要使用Kotlin中的擴充套件函式?
  • 2、怎麼去使用擴充套件函式和擴充套件屬性?
  • 3、什麼是擴充套件函式和屬性?
  • 4、擴充套件函式和成員函式區別
  • 5、擴充套件函式不可以被重寫

一、為什麼要使用Kotlin中的擴充套件函式

我們都知道在Koltin這門語言可以與Java有非常好的互操作性,所以擴充套件函式這個新特性可以很平滑與現有Java程式碼整合。甚至純Kotlin的專案都可以基於Java庫,甚至Android中的一些框架庫,第三方庫來構建。擴充套件函式非常適合Kotlin和Java語言混合開發模式。在很多公司一些比較穩定良好的庫都是Java寫,也完全沒必要去用Kotlin語言重寫。但是想要擴充套件庫的介面和功能,這時候擴充套件函式可能就會派上用場。使用Kotlin的擴充套件函式還有一個好處就是沒有副作用,不會對原有庫程式碼或功能產生影響。先來看下擴充套件函式長啥樣

  • 給TextView設定加粗簡單的例子
//擴充套件函式定義
fun TextView.isBold() = this.apply { 
	paint.isFakeBoldText = true
}

//擴充套件函式呼叫
activity.find<TextView>(R.id.course_comment_tv_score).isBold()
複製程式碼

二、怎麼去使用擴充套件函式和擴充套件屬性

  • 1、擴充套件函式的基本使用

只需要把擴充套件的類或者介面名稱,放到即將要新增的函式名前面。這個類或者名稱就叫做接收者型別,類的名稱與函式之間用"."呼叫連線。this指代的就是接收者物件,它可以訪問擴充套件的這個類可訪問的方法和屬性。

淺談Kotlin語法篇之擴充套件函式(五)

注意: 接收者型別是由擴充套件函式定義的,而接收者物件正是這個接收者型別的物件例項,那麼這個物件例項就可以訪問這個類中成員方法和屬性,所以一般會把擴充套件函式當做成員函式來用。

  • 2、擴充套件屬性的基本使用 擴充套件屬性實際上是提供一種方法來訪問屬性而已,並且這些擴充套件屬性是沒有任何的狀態的,因為不可能給現有Java庫中的物件額外新增屬性欄位,只是使用簡潔語法類似直接操作屬性,實際上還是方法的訪問。
//擴充套件屬性定義
var TextView.isBolder: Boolean
	get() {//必須定義get()方法,因為不能在現有物件新增欄位,也自然就沒有了預設的get()實現
		return this.paint.isFakeBoldText
	}
	set(value) {
		this.paint.isFakeBoldText = value
	}
//擴充套件屬性呼叫
activity.find<TextView>(R.id.course_comment_tv_score).isBolder = true
複製程式碼

注意:

  • 擴充套件屬性和擴充套件函式定義類似,也有接收者型別和接收者物件,接收者物件也是接收者型別的一個例項,一般可以把它當做類中成員屬性來使用。

  • 必須定義get()方法,在Kotlin中類中的屬性都是預設新增get()方法的,但是由於擴充套件屬性並不是給現有庫中的類新增額外的屬性,自然就沒有預設get()方法實現之說。所以必須手動新增get()方法。

  • 由於重寫了set()方法,說明這個屬性訪問許可權是可讀和可寫,需要使用var

三、什麼是擴充套件函式和屬性

我們從上面例子可以看出,kotlin的擴充套件函式真是強大,可以毫無副作用給原有庫的類增加屬性和方法,比如例子中TextView,我們根本沒有去動TextView原始碼,但是卻給它增加一個擴充套件屬性和函式。具有那麼強大功能,到底它背後原理是什麼?其實很簡單,通過decompile看下反編譯後對應的Java程式碼就一目瞭然了。

  • 1、擴充套件函式實質原理

擴充套件函式實際上就是一個對應Java中的靜態函式,這個靜態函式引數為接收者型別的物件,然後利用這個物件就可以訪問這個類中的成員屬性和方法了,並且最後返回一個這個接收者型別物件本身。這樣在外部感覺和使用類的成員函式是一樣的。

public final class ExtendsionTextViewKt {//這個類名就是頂層檔名+“Kt”字尾,這個知識上篇部落格有詳細介紹
   @NotNull
   public static final TextView isBold(@NotNull TextView $receiver) {//擴充套件函式isBold對應實際上是Java中的靜態函式,並且傳入一個接收者型別物件作為引數
      Intrinsics.checkParameterIsNotNull($receiver, "$receiver");
      $receiver.getPaint().setFakeBoldText(true);//設定加粗
      return $receiver;//最後返回這個接收者物件自身,以致於我們在Kotlin中完全可以使用this替代接收者物件或者直接不寫。
   }
}
複製程式碼
  • 2、Java中呼叫Kotlin中定義的擴充套件函式

分析完Kotlin中擴充套件函式的原理,我們也就很清楚,如何在Java中去呼叫Kotlin中定義好的擴充套件函式了,實際上使用方法就是靜態函式呼叫,和我們之前講的頂層函式在Java中呼叫類似,不過唯一不同是需要傳入一個接收者物件引數。

ExtendsionTextViewKt.isBold(activity.findViewById(R.id.course_comment_tv_score));//直接呼叫靜態函式
複製程式碼
  • 3、擴充套件屬性實質原理

擴充套件屬性實際上就是提供某個屬性訪問的set,get方法,這兩個set,get方法是靜態函式,同時都會傳入一個接收者型別的物件,然後在其內部用這個物件例項去訪問和修改物件所對應的類的屬性。

public final class ExtendsionTextViewKt {
   //get()方法所對應生成靜態函式,並且傳入一個接收者型別物件作為引數
   public static final boolean isBolder(@NotNull TextView $receiver) {
      Intrinsics.checkParameterIsNotNull($receiver, "$receiver");
      return $receiver.getPaint().isFakeBoldText();
   }
   //set()方法所對應生成靜態函式,並且傳入一個接收者型別物件作為引數和一個需要set的引數
   public static final void setBolder(@NotNull TextView $receiver, boolean value) {
      Intrinsics.checkParameterIsNotNull($receiver, "$receiver");
      $receiver.getPaint().setFakeBoldText(true);
   }
}
複製程式碼
  • 4、Java中呼叫Kotlin中定義的擴充套件屬性

Java呼叫Kotlin中定義的擴充套件屬性也很簡單,就相當於直接呼叫生成的set(),get()方法一樣。

    ExtendsionTextViewKt.setBolder(activity.findViewById(R.id.course_comment_tv_score), true);
複製程式碼

四、擴充套件函式和成員函式區別

說到擴充套件函式和成員函式的區別,通過上面例子我們已經很清楚了,這裡做個歸納總結:

  • 1、擴充套件函式和成員函式使用方式類似,可以直接訪問被擴充套件類的方法和屬性。(原理: 傳入了一個擴充套件類的物件,內部實際上是用例項物件去訪問擴充套件類的方法和屬性)
  • 2、擴充套件函式不能打破擴充套件類的封裝性,不能像成員函式一樣直接訪問內部私有函式和屬性。(原理: 原理很簡單,擴充套件函式訪問實際是類的物件訪問,由於類的物件例項不能訪問內部私有函式和屬性,自然擴充套件函式也就不能訪問內部私有函式和屬性了)
  • 3、擴充套件函式實際上是一個靜態函式是處於類的外部,而成員函式則是類的內部函式。
  • 4、父類成員函式可以被子類重寫,而擴充套件函式則不行

五、擴充套件函式不可以被重寫

在Kotlin和Java中我們都知道類的成員函式是可以被重寫的,子類是可以重寫父類的成員函式,但是子類是不可以重寫父類的擴充套件函式。

open class Animal {
    open fun shout() = println("animal is shout")//定義成員函式
}

class Cat: Animal() {
    override fun shout() {
        println("Cat is shout")//子類重寫父類成員函式
    }
}

//定義子類和父類擴充套件函式
fun Animal.eat() = println("Animal eat something")

fun Cat.eat()= println("Cat eat fish")

//測試
fun main(args: Array<String>) {
    val animal: Animal = Cat()
    println("成員函式測試: ${animal.shout()}")
    println("擴充套件函式測試: ${animal.eat()}")
}
複製程式碼

執行結果:

淺談Kotlin語法篇之擴充套件函式(五)

以上執行結果再次說明了擴充套件函式並不是類的一部分,它是宣告與類外部的,儘管子類和父類擁有了相同的擴充套件函式,但是實際上擴充套件函式是靜態函式。從編譯內部來看,子類和父類擁有了相同的擴充套件函式,實際上就是定義兩個同名的靜態擴充套件函式分別傳入父類物件和子類物件,那麼呼叫的方法肯定也是父類中的方法和子類中的方法,所以輸出肯定是父類的。

淺談Kotlin語法篇之擴充套件函式(五)

歡迎關注Kotlin開發者聯盟,這裡有最新Kotlin技術文章,每週會不定期翻譯一篇Kotlin國外技術文章。如果你也喜歡Kotlin,歡迎加入我們~~~

相關文章