Kotlin学习教程之函数的默认参数

前言

​ 在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函数的默认参数内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • 利用 kotlin 的方式自定义回调事件(kotlin函数参数)

    java 中自定义回调事件的写法 创建 interface类,创建 interface 对象,实现 set 方法: 使用: kotlin 中自定义点击事件写法 依照 java 的思想(不推荐) 创建 interface类,创建 interface 可变对象(var) 使用: 利用 kotlin 函数作为参数(强烈推荐) 来看一下系统的点击事件在 kotlin 中是什么样的: 是不是简直简洁到不像话?再看看你自己定义的点击事件回调,感觉跟还在用 java 开发一样- 下面就来看个新的写法: 创建一

  • Kotlin学习教程之函数的默认参数

    前言 ​ 在Java中,为函数的参数添加默认值是不被允许的,这是为了防止默认参数与函数重载同时使用时二义性的问题,考虑下面的例子: void func(p1: String, p2: String, p3: String = "default") { // do something } void func(String p1, String p2) { // do something } 假设上面的代码是可以编译通过的,那么当调用func("p1","p2

  • Python函数的默认参数设计示例详解

    在Python教程里,针对默认参数,给了一个"重要警告"的例子: def f(a, L=[]): L.append(a) return L print(f(1)) print(f(2)) print(f(3)) 默认值只会执行一次,也没说原因.会打印出结果: [1] [1, 2] [1, 2, 3] 因为学的第一门语言是Ruby,所以感觉有些奇怪. 但肯定的是方法f一定储存了变量L. 准备知识:指针 p指向不可变对象,比如数字.则相当于p指针指向了不同的内存地址. p指向的是可变对象,

  • Python中函数及默认参数的定义与调用操作实例分析

    本文实例讲述了Python中函数及默认参数的定义与调用操作.分享给大家供大家参考,具体如下: #coding=utf8 ''''' Python中的函数使用小括号调用.函数在调用之前必须先定义. 如果函数中没有return语句,就会自动返回None对象. Python是通过引用调用的.如果函数内对参数的改变会影响到原始对象. 只有可变对象会受此影响,对不可变对象,它的行为类似按值调用. ''' ''''' 定义函数: def function_name([arguments]): "option

  • Kotlin基础教程之函数定义与变量声明

    Kotlin基础教程之函数定义与变量声明 可以看到,函数定义就是 <访问控制符> <函数名> <参数列表> <:返回类型(不写就是无返回类型)> { 函数体 } 单语句函数可以简写,比如add函数和add1函数效果是一样的 变量定义 var <标识符> : <类型> = <初始化值> 常量定义 val <标识符> : <类型> = <初始化值> 常量与变量都可以没有初始化值,但是在引用前

  • 深入解析C++中的函数模板和函数的默认参数

    C++函数模板 我们知道,数据或数值可以通过函数参数传递,在函数定义时它们是未知的,只有在发生函数调用时才能确定其值.这就是数据的参数化. 其实,数据类型也可以通过参数来传递,在函数定义是可以不指明具体的数据类型,当发生函数调用时,编译器可以根据传入的参数自动确定数据类型.这就是数据类型参数化. 所谓函数模板,实际上是建立一个通用函数,其返回值类型和形参类型不具体指定,用一个虚拟的类型来代替(实际上是用一个标识符来占位).这个通用函数就称为函数模板(Function Template).凡是函数

  • C++中函数的默认参数详细解析

    使用方法:(1)在函数声明或定义时,直接对参数赋值,该参数就是默认参数.(2)在函数调用时,省略部分或全部参数,这时就会使用默认参数进行代替. 注意事项:(1)一般在声明函数是设置默认参数. 如果在函数声明和定义函数时都设置了默认参数,则以函数声明的默认参数为准. 复制代码 代码如下: #include<iostream>using namespace std;int main(){ double add(double a=3.2,double b=9.6);//在函数声明时设置默认参数 co

  • 从汇编看c++函数的默认参数的使用说明

    在c++中,可以为函数提供默认参数,这样,在调用函数的时候,如果不提供参数,编译器将为函数提供参数的默认值.下面从汇编看其原理. 下面是c++源码: 复制代码 代码如下: int add(int a = 1, int b = 2) {//参数a b有默认值    return a + b;}int main() {   int c= add();//不提供参数 } 下面是mian函数里面的汇编码: 复制代码 代码如下: ; 4    : int main() { push    ebp    m

  • python教程对函数中的参数进行排序

    内建的 sorted() 函数可接受一个用来传递可调用对象( callable ) 的参数 key,而该可调用对象会返回待排序对象中的某些值,sorted则利用这些值来比较对象. 例如,如果应用中有一系列的 User 对象实例,而我们想通过 user_id 属性来对他们排序,则可以提供一个可调用对象将 User 实例作为输入然后返回 user_id. class User: def __init__(self, user_id): self.user_id = user_id def __rep

  • C++函数的默认参数详情

    文章转自微信 公众号:Coder梁(ID:Coder_LT) C++当中的支持默认参数,如果你学过Python,那么想必对此不会陌生.C++中的默认参数的用法和Python基本一致. 使用默认参数的方法非常简单,也就是我们在函数声明的时候,就为某些参数指定好默认值.当我们调用函数的时候,如果没有传入对应的参数,那么则使用默认值. 比如: void func(int a, int b=2, int c=3, int d=4) { cout << a << " "

  • python函数的默认参数请勿定义可变类型详解

    目录 函数的默认参数请勿定义可变类型 可变类型和不可变类型 定义可变类型会有什么问题? 导致的原因 解决方法 关于可变类型作为默认参数时的注意点 测试:将可变类型列表换为字典 测试:来个不可变类型字符串 测试:元祖包个列表来 小结一下 函数的默认参数请勿定义可变类型 经常会看到这样一句代码警告: Default argument value is mutable 意思是告诉我们函数的定义中,使用可变类型做默认参数. 那为什么会有这个警告呢? 可变类型和不可变类型 可变类型(mutable):列表

随机推荐