1. 程式人生 > >kotlin學習day11:擴充套件函式

kotlin學習day11:擴充套件函式

想一想,當我們使用Java開發Android的時候有沒有遇到過這種場景:假設我們需要給某個類新增一個通用方法的時候,是不是必須繼承這個類,然後去自定義我們的方法。例如我們要給TextView新增一個設定text的方法,我們就必須:

public class SuperTextView extends TextView {

    public SuperTextView(Context context) {
        super(context);
    }

    public SuperTextView(Context context, @Nullable AttributeSet attrs) {
        super
(context, attrs); } public SuperTextView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); } public SuperTextView(Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) { super(context, attrs, defStyleAttr, defStyleRes); } /** * 自定義一個text方法 */
public void text(String text) { if (!text.isEmpty()) { this.setText(text); } } }

是不是很麻煩,先得寫一堆建構函式(其實我們並沒有對建構函式做特別的處理,所以在這裡是不需要的),然後才可以寫我們的方法,然而在Kotlin中,我們只需要:在一個檔案中宣告一個函式:

fun TextView.text(text:String){
    this.text = text
}

然後我們就可以對任意的TextView使用這個函式,其中放置在函式名稱之前的類或者介面的名字就是我們期望去擴充套件的類或介面,在這裡 即是TextView,叫做接收器型別

,而呼叫的擴充套件函式的值,即這個this叫做接收器物件

擴充套件函式的概念:一個可以作為一個類成員進行呼叫的函式,但是定義在這個類的外部。

在擴充套件函式中,我們可以直接訪問所擴充套件類的函式和屬性,但這並不意味著我們可以打破封裝,跟定義在類中的方法不同,擴充套件函式不允許訪問私有或保護訪問屬性的類成員。

匯入擴充套件函式

當定義一個擴充套件函式的時候,它並不會自動的在整個專案中變為可用,需要被我們像其他函式那樣匯入,這在一定程度上也可以避免命名衝突,在Kotlin中,允許Kotlin允許使用用於類的同樣單獨語法來匯入單獨的函式:

import package com.example.admin.kotlindemo.kotlin.text
textview.text("LBJFan")

當然,使用萬用字元( * )匯入也是可以的:

import package com.example.admin.kotlindemo.kotlin.*

同時,kotlin頁提供了as 關鍵詞來該改變我們所匯入的類或者函式的名字:
例如:在這個類中我們 將TextView的擴充套件函式text命名為setTextValue

import com.example.admin.kotlindemo.kotlin.text as setTextValue
textview.setTextValue("LBJFan")

當在不同的包中有多個同名的函式並且你想在同一個檔案中使用它們時,在匯入中改變一個名字是非常有用的,對於常規的類和函式,在這種情況下有另外的選擇:可以使用完全有效的名字來有用類或者函式。對於擴充套件函式,這個語法要求你使用縮寫名字,因此
一個匯入宣告中的as關鍵詞是解決衝突的唯一方法。

擴充套件函式的呼叫

呼叫一個擴充套件函式並沒有涉及介面卡物件的建立或者任何其他執行時開銷。在底層方面,一個擴充套件函式是一個接受接收器物件作為第一個引數的靜態方法。 這使得從Java中使用擴充套件函式變得非常容易:呼叫靜態方法並傳遞接收器物件例項

例如上面的呼叫:

textview.setTextValue("LBJFan")//textviw是一個TextView物件

我們都知道Jvm只能在類中執行程式碼,而Kotlin也是通過JVM執行的,所以當我們在kt檔案中宣告一個函式被呼叫時,編譯器是如何工作的呢?假設text擴充套件函式宣告的檔案叫做TextUtils.kt,它被編譯成Java類程式碼以後:

package com.example.admin.kotlindemo.kotlin;
public class TextUtils {//對應擴充套件函式所在的檔名-TextUtil.kt
    public static void text(.....){.....}
}

可以看到Kotlin編譯器產生的類的名字跟包含函式的檔案的名字一樣。檔案中所有的頂層函式(宣告在一個檔案中的函式)都會被編譯成這個類的靜態方法。因此,從Java中呼叫這個方法跟呼叫其他靜態方法一樣:假設我們的TextUtils檔案中還有一個函式

fun test() {
//dosomething
}

當我們在Java中呼叫時我們只需要:

TextUtilsKt.test();

不可覆蓋的擴充套件函式

方法覆蓋在Kotlin對平常的成員函式是有效的,但是你不能覆蓋一個擴充套件函式。比如說你有兩個類, View 和它的子類 Button ,同時 Button 類覆蓋了來自父類的 click 函式:

open class View {
open fun click() = println("View clicked")
}
class Button: View() { // 1 Button類繼承自View類
override fun click() = println("Button clicked")
}

如果你聲明瞭一個View型別的變數,你也可以在這個變數中儲存 Button 型別的值,因為 Button 是View的子類。如果你用這個變數呼叫了一個常規方法,比如 click() ,而且這個方法在Button類中被覆蓋了。這個來自 Button 類的覆蓋的實現將會被使用:

val view: View = Button()
view.click() // 1 Button類的示例的方法被呼叫。

但是對於擴充套件函式而言:擴充套件函式並不是類的一部分。它們是宣告在類的外部的。儘管你可以為某個基類和它的子類用同樣的名字和引數型別來定義擴充套件函式,被呼叫的函式依賴於已被宣告的靜態型別的變數,而不是依賴於變數值的執行時型別,例如:

fun View.showOff() = println("I'm a view!")
fun Button.showOff() = println("I'm a button!")
val view: View = Button()
view.showOff() // 1 擴充套件函式被靜態的方式進行解析
I'm a view!

當你用一個 View 型別的變數呼叫 showOff 時,對應的擴充套件將會被呼叫,儘管這個值的真實型別是 Button。因此:覆蓋對於擴充套件函式來說是不起作用的:Kotlin以靜態解析它們。

注:如果類有一個成員函式跟一個擴充套件函式有著相同的簽名,成員函式總是優先的。當你擴充套件類的API時,你應該記住這一點:如果你添加了一個跟你已定義類的客戶端(呼叫者)的擴充套件函式具有同樣的簽名成員函式,同時客戶端隨後重新編譯了他們的程式碼,它將會改變它的含義並開始指向一個新的成員函式

示例:
加入我們Collection新增一個擴充套件函式,按照一定的格式去列印它裡面的元素:

fun <T> Collection<T>.joinToString(collection: Collection<T>,separator: String,prefix: String,postfix: String): String {
    val result = StringBuilder(prefix)
    for ((index, element) in collection.withIndex()) {
        if (index > 0) result.append(separator) // 1 在第一個元素之前不新增分隔符
        result.append(element)
    }
    result.append(postfix)
    return result.toString()
}

這個函式是支援泛型的:它對包含任意型別的集合有效
使用:

val list = listOf(1, 2, 3)
println(joinToString(list, "; ", "(", ")"))
(1; 2; 3)

上面這個函式是實現了我們所需要的功能,但是作為一個有追求的程式設計師,我們怎能容忍它的可讀性如此之差呢(引數列表過長,不知道引數是幹嘛的)?這時候我們就需要用到以前說過的指定函式引數的名稱並給他我們需要的預設值,因此他可以變成:

fun <T> joinToString(collection: Collection<T>,
                     separator: String = ", ", // 1 預設的引數值
                     prefix: String = "",
                     postfix: String = ""): String {
    val result = StringBuilder(prefix)
    for ((index, element) in collection.withIndex()) {
        if (index > 0) result.append(separator) // 1 在第一個元素之前不新增分隔符
        result.append(element)
    }
    result.append(postfix)
    return result.toString()
}

使用:

joinToString(list, ", ", "", "")
1, 2, 3
joinToString(list)
1, 2, 3
joinToString(list, "; ")
1; 2; 3

當使用常規呼叫語法是,我們可以只忽略後面的引數。如果使用命名引數,我們可以忽略列表中的一些(其他)引數而僅僅指定你需要的那些(引數)。

以上就是對Kotlin中擴充套件函式的總結,理解不到之處,請指正!