前言
在Java中,为函数的参数添加默认值是不被允许的,这是为了防止默认参数与函数重载同时使用时二义性的问题,考虑下面的例子:
void func(p1: String, p2: String, p3: String = "default") { // do something } void func(String p1, String p2) { // do something }
假设上面的代码是可以编译通过的,那么当调用func("p1","p2") 时,编译器会不知道到底该调用哪个方法。所以Java是不支持默认参数的,但是依然可以通过函数重载的方式实现默认参数的功能,这也是我们最普遍使用的方式:
void func(String p1, String p2, String p3) { // do something } void func(String p1, String p2) { func(a, b, "default"); }
通过上述函数重载的方式,也可以实现默认参数,但是有个问题也很明显,就是如果要支持默认参数,我们需要写很多的模版代码,好像也不是那么方便。然而,Kotlin 提供了默认参数的支持,接下来看看Kotlin中对于默认参数的支持是怎样的,又是怎么解决我们开始提到的那个二义性的问题的。
使用
在Kotlin中,使用默认参数也很简单,在函数定义中直接赋值即可:
fun func(p1: String, p2: String, p3: String = "default") { // do something }
上述函数定义中,c 的默认值就是default,可以这样去调用 func("p1","p2")。同样的,针对构造函数,也可以指定默认值:
class TestDefaultParameters ( val name: String, val type: String = "default" ){}
那么如果想要在Java中调用kotlin带有默认参数的函数怎么做呢?如果在Java中直接调用func("p1","p2")编译器会报错,这是需要给kotlin的方法加上Jvm重载的注解就可以了:
@JvmOverloads fun func(p1: String, p2: String, p3: String = "default") { // do something }
解析
接下来,我们看看Kotlin是如何实现默认参数的,首先,写一个例子如下:
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的函数定义Decompile为Java实现:
@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); } ... // 调用func函数 TestDefaultParameters.func$default(testDefaultParameters, "position1", "position2", (String)null, 4, (Object)null);
中间一些代码我省略了,可以看到,Kotlin编译器为我们生成了三个func的重载方法,下面我们依次来看一下分别都是什么函数:
- 首先看到的第一个函数是带有三个参数的func,函数内部都做了空安全的检查,这是kotlin的特性,由于声明函数时参数都是不为空的,所以这里需要检查参数是否为空,会抛出异常。
- 第二个函数我们看到名字是func$default, 并不是func的方法重载,而是一个新的方法,这就是默认参数实现的关键方法,这里暂且按下不表,后面详细讲解。
- 第三个函数依然是func方法的重载,可以看到这个方法只有两个参数,并且内部调用了第二个方法。其实这个方法是给Java调用的,由于我们将func函数声明为@JvmOverloads,所以当Java在不传递默认参数调用func的时候,实际上调用的是这个方法。如果将@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个参数,var0为Class对象,var1 ~var3 分别对应func函数的三个参数,然后有一个int类型的var4和一个Object类型的 var5。var5这个大多数情况下都为null,默认参数实现的秘密主要是在这个var 4上, 来看看当调用函数使用默认参数时,是怎么调用的:
// kotlin func("position1", "position2") // Decompile func$default(testDefaultParameters, "position1", "position2", (String)null, 4, (Object)null)
看到var4的值为4。是由于原函数是第三个参数为默认参数,即 position = 2位置的参数,所以 var4 = 222^222=4
在看之前func$default 的方法实现:
if ((var4 & 4) != 0) { var3 = "default"; }
当var4 & 4 != 0的时候,var3的值就等于默认参数。可以发现,func$default函数的int类型的参数就是表示第几个参数的值是默认参数的。下面看一个稍微复杂的例子:
fun func(p1: String = "position1", p2: String = "position2", p3: String = "position3") { // do something } // 调用 testDefaultParameters.func(p2 = "position2")
这次三个参数都有默认值,且调用时用具名参数指定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); } // 调用 func$default(testDefaultParameters, (String)null, "position2", (String)null, 5, (Object)null)
可以看到,这次方法体内有三个判断,因为有三个参数都是有默认值的,传递的参数为5,是由于函数调用时,index=0 和 index=2的参数为默认参数,所以 var4 = 20+222^0 + 2^220+22 = 5。
这里大概解释一下为什么要这么设计的原因:
当写有多个条件,例如权限判断,index判断等逻辑的时候非常适合位运算。例如在上面的例子中,参数的index可以表示为:2的index幂的二进制数,例如 index = 0 即 202^020 , 用二进制表示为:0001,index = 1 即 212^121 ,表示为:0010(可以看作是二进制数中1的位置,即表示index)。
那么如果多个位置比如index=0与index=2呢?既可以表示为:0101。就是 20+22=52^0 + 2^2 = 520+22=5 。与目标所在的index进行按位与运算的时候,如果不等于0就表示该index符合条件。否则不符合。
回过头来看上述func$default函数体就清晰了,就是通过位置判断,当不是使用默认值的位置时,就不使用默认值。
上述例子中,我们看到含有默认值的参数的函数在Decompile之后,有一个始终为null的Object参数,而且也没有被使用到。那么这个参数有什么用呢?这个参数会在尝试重写有默认参数的函数时用到。例如下面的例子:
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); } }
可以看到,当调用超类使用默认参数在当前版本是不允许的(以后可能允许)。这就是Object参数的用处。
总结
以上就是对kotlin的默认参数实现的一些总结。
到此这篇关于Kotlin学习教程之函数的默认参数的文章就介绍到这了,更多相关Kotlin函数的默认参数内容请搜索自由互联以前的文章或继续浏览下面的相关文章希望大家以后多多支持自由互联!