奇技淫巧之Kotlin 擴充套件函式(一)

JackBiao發表於2018-07-14

關鍵字:Kotlin擴充套件(Extension)、inline,Kotlin反編譯Java 原始碼

1. 範例

廢話少說,先上範例,來看看擴充套件函式有什麼用。

需求:

  1. 將任意物件轉呼為json
  2. 在任意物件中新增列印日誌方法,列印的日誌使用類名做為TAG

實現程式碼 新建:Any+Extension.kt

val gson
    get() = Gson()

fun Any.log(msg: String) {
    Log.d("_" + this::class.java.simpleName, msg)
}

fun Any.toJson(): String {
    return gson.toJson(this)
}
複製程式碼

測試程式碼

@RunWith(AndroidJUnit4::class)
class ExampleInstrumentedTest {
    data class Person(val name: String, val age: Int, val gender: String, val interest: String)
    @Test
    fun extendTestCase() {1
        val person = Person("jack", 18, "男", "愛好女")
        //呼叫log 的物件是ExampleInstrumentedTest因此TAG 日誌列印的TAG是\ExampleInstrumentedTest
        log(person.toJson())
    }
}

複製程式碼

2. java 實現

如果使用java 實現上面列印日誌和toJson的需求的方式就是建立一個工具類

public class Utils {

    public static final void log(Object target, String msg) {
        Log.d(target.getClass().getSimpleName(), msg);
    }

    public static final String toJson(Object target) {
        return new Gson().toJson(target);
    }
}
複製程式碼

3. Kotlin 擴充套件函式原理

通過範例,我們已經知道Kotlin擴充套件函式怎麼用了,現在我們來探討下Kotlin擴充套件函式的實現原理。我們知道java 是靜態型別語言,無法在執行時改變其類結構(函式、屬性),只能通過繼承的方式創造新類來擴充套件屬性和方法,Kotlin 也是靜態語言,也同樣無法在執行時改變其類結構(函式、屬性)。那麼,Kotlin 的擴充套件函式有是怎麼實現的呢? 。我們利用Android studio 的Kotlin Tools 將Kotlin程式碼,反編譯為Java原始碼:

(kotlin-android 會將Kotlin 原始碼,編譯成 java class 檔案,從而執行在jvm 上面)

image

image

上圖是反編譯後的Java 原始碼,是不是一切都清楚了,和我們上面的Java 工具類的實現是一樣的。

4. 使用inline (行內函數)修飾符優化使用頻繁的擴充套件函式的呼叫效率

我們寫兩個實現同樣功能的擴充套件函式,一個不用inline 修飾,一個用inline 修飾。

fun Any.log(msg: String) {
    Log.d("_" + this::class.java.simpleName, msg)
}
inline fun  Any.log1(msg: String) {
    Log.d("_" + this::class.java.simpleName, msg)
}

複製程式碼

呼叫這兩個方法的程式碼

@Test
    fun extendTestCase() {
        val person = Person("jack", 18, "男", "愛好女")
        log(person.toJson())
        log1(person.toJson())
    }
複製程式碼

反編譯Kotlin 位元組碼檢視對應的Java 實現

image

可以看到 :

在呼叫log1(person.toJson()) 時是直接將 log1 的函式體(內部實現)複製了過來。

why not?下面我們來看行內函數的說明

程式在順序執行的過程中遇到函式呼叫,首先要保護現場(壓棧)->跳轉到函式執行處->恢復現場(出棧)(此處並沒有詳細展開大概知道流程即可),這樣一進一出無疑損耗了效能,於是乎產生了行內函數。 行內函數並沒有壓棧出棧的操作,而是直接將行內函數的函式體複製貼上到了呼叫的位置,這樣雖然減少了效能損耗,但是編譯過程中實際的程式碼量變大了,這就是簡單的空間換時間。 簡而言之:行內函數就將其函式體複製貼上過來 行內函數的說明見:https://www.jianshu.com/p/7cb426ee324c,通俗易懂

5. 小結

  1. 使用Kotlin 後我們可以使用擴充套件函式代替以前的工具類方便的實現一些通用的工具方法,例如列印log、toJson,showToast 等等。
  2. 在Android 上面的Kotlin 都是編譯Java class 檔案,我們可以利用Android studio 的Kotlin Tools 將Kotlin程式碼,反編譯為Java原始碼來了解Kotlin 的內部實現,特別是在使用Kotlin遇到問題時,可以通過這種方式來定位問題。
  3. 對於使用頻繁的擴充套件函式,可以使用inline 修飾符,優化函式的呼叫效率。

相關文章