Kotlin學(xué)習(xí)教程之函數(shù)的默認(rèn)參數(shù)
前言
在Java中,為函數(shù)的參數(shù)添加默認(rèn)值是不被允許的,這是為了防止默認(rèn)參數(shù)與函數(shù)重載同時使用時二義性的問題,考慮下面的例子:
void func(p1: String, p2: String, p3: String = "default") { // do something } void func(String p1, String p2) { // do something }
假設(shè)上面的代碼是可以編譯通過的,那么當(dāng)調(diào)用func("p1","p2") 時,編譯器會不知道到底該調(diào)用哪個方法。所以Java是不支持默認(rèn)參數(shù)的,但是依然可以通過函數(shù)重載的方式實現(xiàn)默認(rèn)參數(shù)的功能,這也是我們最普遍使用的方式:
void func(String p1, String p2, String p3) { // do something } void func(String p1, String p2) { func(a, b, "default"); }
通過上述函數(shù)重載的方式,也可以實現(xiàn)默認(rèn)參數(shù),但是有個問題也很明顯,就是如果要支持默認(rèn)參數(shù),我們需要寫很多的模版代碼,好像也不是那么方便。然而,Kotlin 提供了默認(rèn)參數(shù)的支持,接下來看看Kotlin中對于默認(rèn)參數(shù)的支持是怎樣的,又是怎么解決我們開始提到的那個二義性的問題的。
使用
在Kotlin中,使用默認(rèn)參數(shù)也很簡單,在函數(shù)定義中直接賦值即可:
fun func(p1: String, p2: String, p3: String = "default") { // do something }
上述函數(shù)定義中,c 的默認(rèn)值就是default,可以這樣去調(diào)用 func("p1","p2")。同樣的,針對構(gòu)造函數(shù),也可以指定默認(rèn)值:
class TestDefaultParameters ( val name: String, val type: String = "default" ){}
那么如果想要在Java中調(diào)用kotlin帶有默認(rèn)參數(shù)的函數(shù)怎么做呢?如果在Java中直接調(diào)用func("p1","p2")編譯器會報錯,這是需要給kotlin的方法加上Jvm重載的注解就可以了:
@JvmOverloads fun func(p1: String, p2: String, p3: String = "default") { // do something }
解析
接下來,我們看看Kotlin是如何實現(xiàn)默認(rèn)參數(shù)的,首先,寫一個例子如下:
fun main(args: Array<String>) { val testDefaultParameters = TestDefaultParameters("") testDefaultParameters.func("position1", "position2") } class TestDefaultParameters ( val name: String, val type: String = "default" ){ @JvmOverloads fun func(p1: String, p2: String, p3: String = "default") { // do something } }
將上述的func的函數(shù)定義Decompile為Java實現(xiàn):
@JvmOverloads public final void func(@NotNull String p1, @NotNull String p2, @NotNull String p3) { Intrinsics.checkParameterIsNotNull(p1, "p1"); Intrinsics.checkParameterIsNotNull(p2, "p2"); Intrinsics.checkParameterIsNotNull(p3, "p3"); } // $FF: synthetic method public static void func$default(TestDefaultParameters var0, String var1, String var2, String var3, int var4, Object var5) { if ((var4 & 4) != 0) { var3 = "default"; } var0.func(var1, var2, var3); } @JvmOverloads public final void func(@NotNull String p1, @NotNull String p2) { func$default(this, p1, p2, (String)null, 4, (Object)null); } ... // 調(diào)用func函數(shù) TestDefaultParameters.func$default(testDefaultParameters, "position1", "position2", (String)null, 4, (Object)null);
中間一些代碼我省略了,可以看到,Kotlin編譯器為我們生成了三個func的重載方法,下面我們依次來看一下分別都是什么函數(shù):
- 首先看到的第一個函數(shù)是帶有三個參數(shù)的func,函數(shù)內(nèi)部都做了空安全的檢查,這是kotlin的特性,由于聲明函數(shù)時參數(shù)都是不為空的,所以這里需要檢查參數(shù)是否為空,會拋出異常。
- 第二個函數(shù)我們看到名字是func$default, 并不是func的方法重載,而是一個新的方法,這就是默認(rèn)參數(shù)實現(xiàn)的關(guān)鍵方法,這里暫且按下不表,后面詳細(xì)講解。
- 第三個函數(shù)依然是func方法的重載,可以看到這個方法只有兩個參數(shù),并且內(nèi)部調(diào)用了第二個方法。其實這個方法是給Java調(diào)用的,由于我們將func函數(shù)聲明為@JvmOverloads,所以當(dāng)Java在不傳遞默認(rèn)參數(shù)調(diào)用func的時候,實際上調(diào)用的是這個方法。如果將@JvmOverloads去掉的話,是沒有這個方法的。
在了解了三個方法的作用之后,主要來看一下第二個方法:
// $FF: synthetic method public static void func$default(TestDefaultParameters var0, String var1, String var2, String var3, int var4, Object var5) { if ((var4 & 4) != 0) { var3 = "default"; } var0.func(var1, var2, var3); }
可以看到這個方法有6個參數(shù),var0為Class對象,var1 ~var3 分別對應(yīng)func函數(shù)的三個參數(shù),然后有一個int類型的var4和一個Object類型的 var5。var5這個大多數(shù)情況下都為null,默認(rèn)參數(shù)實現(xiàn)的秘密主要是在這個var 4上, 來看看當(dāng)調(diào)用函數(shù)使用默認(rèn)參數(shù)時,是怎么調(diào)用的:
// kotlin func("position1", "position2") // Decompile func$default(testDefaultParameters, "position1", "position2", (String)null, 4, (Object)null)
看到var4的值為4。是由于原函數(shù)是第三個參數(shù)為默認(rèn)參數(shù),即 position = 2位置的參數(shù),所以 var4 = 222^222=4
在看之前func$default 的方法實現(xiàn):
if ((var4 & 4) != 0) { var3 = "default"; }
當(dāng)var4 & 4 != 0的時候,var3的值就等于默認(rèn)參數(shù)??梢园l(fā)現(xiàn),func$default函數(shù)的int類型的參數(shù)就是表示第幾個參數(shù)的值是默認(rèn)參數(shù)的。下面看一個稍微復(fù)雜的例子:
fun func(p1: String = "position1", p2: String = "position2", p3: String = "position3") { // do something } // 調(diào)用 testDefaultParameters.func(p2 = "position2")
這次三個參數(shù)都有默認(rèn)值,且調(diào)用時用具名參數(shù)指定p2的值為"position2"。下面看看Decompile后的代碼:
// $FF: synthetic method public static void func$default(TestDefaultParameters var0, String var1, String var2, String var3, int var4, Object var5) { if ((var4 & 1) != 0) { var1 = "position1"; } if ((var4 & 2) != 0) { var2 = "position2"; } if ((var4 & 4) != 0) { var3 = "position3"; } var0.func(var1, var2, var3); } // 調(diào)用 func$default(testDefaultParameters, (String)null, "position2", (String)null, 5, (Object)null)
可以看到,這次方法體內(nèi)有三個判斷,因為有三個參數(shù)都是有默認(rèn)值的,傳遞的參數(shù)為5,是由于函數(shù)調(diào)用時,index=0 和 index=2的參數(shù)為默認(rèn)參數(shù),所以 var4 = 20+222^0 + 2^220+22 = 5。
這里大概解釋一下為什么要這么設(shè)計的原因:
當(dāng)寫有多個條件,例如權(quán)限判斷,index判斷等邏輯的時候非常適合位運算。例如在上面的例子中,參數(shù)的index可以表示為:2的index冪的二進(jìn)制數(shù),例如 index = 0 即 202^020 , 用二進(jìn)制表示為:0001,index = 1 即 212^121 ,表示為:0010(可以看作是二進(jìn)制數(shù)中1的位置,即表示index)。
那么如果多個位置比如index=0與index=2呢?既可以表示為:0101。就是 20+22=52^0 + 2^2 = 520+22=5 。與目標(biāo)所在的index進(jìn)行按位與運算的時候,如果不等于0就表示該index符合條件。否則不符合。
回過頭來看上述func$default函數(shù)體就清晰了,就是通過位置判斷,當(dāng)不是使用默認(rèn)值的位置時,就不使用默認(rèn)值。
上述例子中,我們看到含有默認(rèn)值的參數(shù)的函數(shù)在Decompile之后,有一個始終為null的Object參數(shù),而且也沒有被使用到。那么這個參數(shù)有什么用呢?這個參數(shù)會在嘗試重寫有默認(rèn)參數(shù)的函數(shù)時用到。例如下面的例子:
open class TestDefaultParameters { open fun func(p1: String = "position1", p2: String = "position2", p3: String = "position3") { // do something } } class TestDefaultChild : TestDefaultParameters() { override fun func(p1: String, p2: String, p3: String) { // do something } }
將上述代碼編譯一下:
// $FF: synthetic method public static void func$default(TestDefaultParameters var0, String var1, String var2, String var3, int var4, Object var5) { if (var5 != null) { throw new UnsupportedOperationException("Super calls with default arguments not supported in this target, function: func"); } else { if ((var4 & 1) != 0) { var1 = "position1"; } if ((var4 & 2) != 0) { var2 = "position2"; } if ((var4 & 4) != 0) { var3 = "position3"; } var0.func(var1, var2, var3); } }
可以看到,當(dāng)調(diào)用超類使用默認(rèn)參數(shù)在當(dāng)前版本是不允許的(以后可能允許)。這就是Object參數(shù)的用處。
總結(jié)
以上就是對kotlin的默認(rèn)參數(shù)實現(xiàn)的一些總結(jié)。
到此這篇關(guān)于Kotlin學(xué)習(xí)教程之函數(shù)的默認(rèn)參數(shù)的文章就介紹到這了,更多相關(guān)Kotlin函數(shù)的默認(rèn)參數(shù)內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Cocos2d-x 3.0中集成社交分享ShareSDK的詳細(xì)步驟和常見問題解決
這篇文章主要介紹了Cocos2d-x 3.0中集成社交分享ShareSDK的詳細(xì)步驟和常見問題的解決方法以及需要注意的問題,需要的朋友可以參考下2014-04-04Android編程實現(xiàn)異步消息處理機(jī)制的幾種方法總結(jié)
這篇文章主要介紹了Android編程實現(xiàn)異步消息處理機(jī)制的幾種方法,結(jié)合實例形式詳細(xì)總結(jié)分析了Android異步消息處理機(jī)制的原理、相關(guān)實現(xiàn)技巧與操作注意事項,需要的朋友可以參考下2018-08-08Android實現(xiàn)本地上傳圖片并設(shè)置為圓形頭像
我們在做項目的時候會用到圓形的圖片,比如用戶頭像,類似QQ。用戶在用QQ更換頭像的時候,上傳的圖片都是矩形的,但顯示的時候確是圓形的。那么這是如何實現(xiàn)的呢,下面我們就來探討下吧。2015-05-05使用DrawerLayout組件實現(xiàn)側(cè)滑抽屜的功能
DrawerLayout組件同樣是V4包中的組件,也是直接繼承于ViewGroup類,所以說是一個容器類,下面通過本文給大家介紹使用DrawerLayout組件實現(xiàn)側(cè)滑抽屜的功能,感興趣的朋友一起看下吧2016-08-08詳解Android?Flutter如何使用相機(jī)實現(xiàn)拍攝照片
在app中使用相機(jī)肯定是再平常不過的一項事情了,相機(jī)肯定涉及到了底層原生代碼的調(diào)用,那么在flutter中如何快速簡單的使用上相機(jī)的功能呢?一起來看看吧2023-04-04Android實現(xiàn)手機(jī)監(jiān)控攝像頭
這篇文章主要為大家詳細(xì)介紹了Android實現(xiàn)手機(jī)監(jiān)控攝像頭,文中示例代碼介紹的非常詳細(xì),具有一定的參考價值,感興趣的小伙伴們可以參考一下2022-03-03Android側(cè)滑菜單之DrawerLayout用法詳解
今天小編就為大家分享一篇關(guān)于Android側(cè)滑菜單之DrawerLayout用法詳解,小編覺得內(nèi)容挺不錯的,現(xiàn)在分享給大家,具有很好的參考價值,需要的朋友一起跟隨小編來看看吧2019-03-03