Kotlin Vocabulary | Kotlin 默认参数
默认参数 是一个简短而易用的功能,它可以让您无需模版代码便可实现函数重载。和 Kotlin 所提供的许多其他功能一样,默认参数会给人一种魔法般的感觉。如果您想要知道其中的奥秘,请继续阅读,本文将会揭晓默认参数内部的工作原理。
基本用法
如果您需要重载一个函数,您可以使用默认参数,而不是将同一个函数实现许多次:
<!-- Copyright 2019 Google LLC. SPDX-License-Identifier: Apache-2.0 --> // 无需像下面这样实现: fun play(toy: Toy){ ... } fun play(){ play(SqueakyToy) } // 使用默认参数: fun play(toy: Toy = SqueakyToy) fun startPlaying() { play(toy = Stick) play() // toy = SqueakyToy }
默认参数也可以应用于构造函数中:
<!-- Copyright 2019 Google LLC. SPDX-License-Identifier: Apache-2.0 --> class Doggo( val name: String, val rating: Int = 11 ) val goodDoggo = Doggo(name = "Tofu") val veryGoodDoggo = Doggo(name = "Tofu", rating = 12)
与 Java 代码相互调用
默认情况下,Java 无法识别默认值重载:
<!-- Copyright 2019 Google LLC. SPDX-License-Identifier: Apache-2.0 --> // kotlin fun play(toy: Toy = SqueakyToy) {... } // java DoggoKt.play(DoggoKt.getSqueakyToy()); DoggoKt.play(); // error: Cannot resolve method 'play()'
您需要在 Kotlin 函数上使用 @JvmOverloads 注解,以指示编译器生成重载方法:
/* Copyright 2020 Google LLC. SPDX-License-Identifier: Apache-2.0 */ @JvmOverloads fun play(toy: Toy = SqueakyToy) {… }
内部实现
让我们通过反编译后的 Java 代码看看编译器为我们生成了什么。您可以在 Android Studio 中选择 Tools -> Kotlin -> Show Kotlin Bytecode
,然后点击 Decompile 按钮:
函数
/* Copyright 2020 Google LLC. SPDX-License-Identifier: Apache-2.0 */ fun play(toy: Toy = SqueakyToy) ... fun startPlaying() { play(toy = Stick) play() // toy = SqueakyToy } // 反编译出的 Java 代码 public static final void play(@NotNull Toy toy) { Intrinsics.checkNotNullParameter(toy, "toy"); } // $FF: synthetic method public static void play$default(Toy var0, int var1, Object var2) { if ((var1 & 1) != 0) { var0 = SqueakyToy; } play(var0); } public static final void startPlaying() { play(Stick); play$default((Toy)null, 1, (Object)null); }
我们可以看到,编译器生成了两个函数:
-
play
—— 该函数有一个参数:Toy
,它会在没有使用默认参数时被调用。 -
play$default
一个合成方法 —— 它有三个参数:Toy
、int
和Object
。只要是使用了默认参数就会被调用。三个参数中的Object
会一直是null
,但是int
的值产生了变化,下面让我们来看看为什么。
int 参数
play$default
函数中 int 参数的值是基于传入的有默认参数的参数数量和其索引计算的。根据这一参数的值,Kotlin 编译器可以知道在调用 play 函数时使用哪个参数。
在我们的 play() 函数的示例代码中,索引位置为 0 的参数使用了默认参数。所以 play$default
在调用时传入的 int 参数为 int var1 = 2⁰
:
play$default((Toy)null, 1, (Object)null);
这样一来,play$default
的实现便可以知道 var0
的值应当被替换为默认值。
为了进一步了解 int 参数的行为,我们来观察一个更为复杂的例子。让我们扩展 play 函数,并在调用时使用 doggo
和 toy 的默认参数:
/* Copyright 2020 Google LLC. SPDX-License-Identifier: Apache-2.0 */ fun play(doggo: Doggo = goodDoggo, doggo2: Doggo = veryGoodDoggo, toy: Toy = SqueakyToy) {...} fun startPlaying() { play2(doggo2 = myDoggo) }
让我们来看看反编译后的代码中发生了什么:
/* Copyright 2020 Google LLC. SPDX-License-Identifier: Apache-2.0 */ public static final void play(@NotNull Doggo doggo, @NotNull Doggo doggo2, @NotNull Toy toy) { ... } // $FF: synthetic method public static void play$default(Doggo var0, Doggo var1, Toy var2, int var3, Object var4) { if ((var3 & 1) != 0) { var0 = goodDoggo; } if ((var3 & 2) != 0) { var1 = veryGoodDoggo; } if ((var3 & 4) != 0) { var2 = SqueakyToy; } play(var0, var1, var2); } public static final void startPlaying() { play2$default((Doggo)null, myDoggo, (Toy)null, 5, (Object)null); }
我们可以看到此时 int 参数的值为 5,它计算的原理为: 位于 0 和 2 的参数使用了默认参数,所以 var3 = 2⁰ + 2² = 5
。使用 按位与操作 对参数进行如下计算:
var3 & 1 != 0
是true
所以var0 = goodDoggo
var3 & 2 != 0
是false
所以var1
没有被替换var3 & 4 != 0
是true
所以var2 = SqueakyToy
通过对 var3 应用位掩码,编译器可以计算出哪个参数应当被替换为默认值。
Object 参数
您也许会注意到,在上面的例子中 Object
参数的值始终为 null,但在 play$default
函数中从未被用到过。该参数与支持重载函数中的默认值有关。
默认参数与继承
当我们想要覆盖某个使用了默认参数的函数时会发生什么呢?
让我们修改上面的示例并:
- 将
play
函数改为Doggo
类型的open
函数,并将Doggo
改为open
类型。 - 创建一个新的类型:
PlayfulDoggo
,该类型继承Doggo
并覆盖play
函数。
当我们尝试在 PlayfulDoggo.play 函数中设置默认值时,会发现这一操作不被允许: 不能为被覆盖的函数的参数设置默认值。
/* Copyright 2020 Google LLC. SPDX-License-Identifier: Apache-2.0 */ open class Doggo( val name: String, val rating: Int = 11 ) { open fun play(toy: Toy = SqueakyToy) {...} } class PlayfulDoggo(val playfulness: Int, name: String, rating: Int) : Doggo(name, rating) { // 错误:不能为被覆盖的函数的参数设置默认值 override fun play(toy: Toy = Stick) { }
如果我们移除覆盖操作符 override
并检查反编译的代码,PlayfulDoggo.play()
函数会变得如下列代码这样:
public void play(@NotNull Toy toy) {... } // $FF: synthetic method public static void play$default(Doggo var0, Toy var1, int var2, Object var3) { if (var3 != null) { throw new UnsupportedOperationException("Super calls with default arguments not supported in this target, function: play"); } else { if ((var2 & 1) != 0) { var1 = DoggoKt.getSqueakyToy(); } var0.play(var1); } }
这是否意味着未来会支持使用默认参数进行 super 调用?我们拭目以待。
构造函数
对于构造函数,反编译后的代码只有一处不同:
/* Copyright 2020 Google LLC. SPDX-License-Identifier: Apache-2.0 */ // kotlin 声明 class Doggo( val name: String, val rating: Int = 11 ) // 反编译后的 Java 代码 public final class Doggo { ... public Doggo(@NotNull String name, int rating) { Intrinsics.checkNotNullParameter(name, "name"); super(); this.name = name; this.rating = rating; } // $FF: synthetic method public Doggo(String var1, int var2, int var3, DefaultConstructorMarker var4) { if ((var3 & 2) != 0) { var2 = 11; } this(var1, var2); }
构造函数同样会创建一个合成方法,但是它在函数中使用了一个空的 DefaultConstructorMarker 对象而不是 Object:
/* Copyright 2020 Google LLC. SPDX-License-Identifier: Apache-2.0 */ // kotlin val goodDoggo = Doggo("Tofu") // 反编译后的 Java 代码 Doggo goodDoggo = new Doggo("Tofu", 0, 2, (DefaultConstructorMarker)null);
就像主构造函数一样,拥有默认参数的次级构造函数也会生成一个使用 DefaultConstructorMarker
的合成方法:
/* Copyright 2020 Google LLC. SPDX-License-Identifier: Apache-2.0 */ // kotlin class Doggo( val name: String, val rating: Int = 11 ) { constructor(name: String, rating: Int, lazy: Boolean = true) } //反编译后的 Java 代码 public final class Doggo { ... public Doggo(@NotNull String name, int rating) { ... } // $FF: synthetic method public Doggo(String var1, int var2, int var3, DefaultConstructorMarker var4) { if ((var3 & 2) != 0) { var2 = 11; } this(var1, var2); } public Doggo(@NotNull String name, int rating, boolean lazy) { ... } // $FF: synthetic method public Doggo(String var1, int var2, boolean var3, int var4, DefaultConstructorMarker var5) { if ((var4 & 4) != 0) { var3 = true; } this(var1, var2, var3); } }
总结
默认参数简单易用,它帮助我们减少了大量处理方法重载所需的模版代码,并允许我们为参数设置默认值。如同许多其他 Kotlin 关键字一样,我们可以通过观察编译器所生成的代码来了解其背后的原理。如果您想要了解更多,请参阅我们 Kotlin Vocabulary 系列 的其他文章。

低调大师中文资讯倾力打造互联网数据资讯、行业资源、电子商务、移动互联网、网络营销平台。
持续更新报道IT业界、互联网、市场资讯、驱动更新,是最及时权威的产业资讯及硬件资讯报道平台。
转载内容版权归作者及来源网站所有,本站原创内容转载请注明来源。
- 上一篇
Kubernetes二进制高可用方案(高可用的部署方法)
一、Kubernetes高可用概述 Kubernetes高可用是保证Master节点中API Server服务的高可用。API Server提供了Kubernetes各类资源对象增删改查的唯一访问入口,是整个Kubernetes系统的数据总线和数据中心。采用负载均衡(Load Balance)连接两个Master节点可以提供稳定容器云业务。 1.1、Kubernetes高可用主机分配 主机名 IP地址 操作系统 主要软件 K8s-master01 192.168,200.111 CentOS7.x Etcd+Kubernetes K8s-master02 192.168.200.112 CentOS7.x Etcd+Kubernetes K8s-node01 192.168.200.113 CentOS7.x Etcd+Kubernetes+Flannel+Docker K8s-node02 192.168.200.114 CentOS7.x Etcd+Kubernetes+Flannel+Docker K8s-lb01 192.168.200.115 CentOS7.x Ngin...
- 下一篇
读 2020 年 Javascript 趋势报告展望 ES2020
2020年是一个不平凡的一年,但已经过去了,总结过去,展望未来! Javascript 在过去一年里整体上在设法向前发展。得益于像可选链(Optional Chaining)和空值合并运算符(Nullish Coalescing)这样的新特性,语言本身在不断改进,而 TypeScript 的广泛使用将静态类型化普及到了一个新的高度。 2021年1月14日,Javascript 2020趋势调查报告发布了。调查结果来自137个国家的23,765名开发者,涵盖了开发者对Javascript特性、技术、工具等的使用和想法。下面来一起看看这份报告,并加深对Javascript的认识,在新的一年里提升一个档次。 2021年Javascript工具 去年,最常用的技术没有发生太大的变化。TypeScript仍然是最常用的Javascript风格,React仍然是最常用的前端库,Express仍然是最常用的后端库。如果你想成为一名Web工程师,那么这些绝对是应该首先学习的技术。 但是,谈到开发人员在2020年最喜欢的技术时,看到了许多新的竞选者,可能也代表着一种未来趋势。 前端框架:Svelte S...
相关文章
文章评论
共有0条评论来说两句吧...
文章二维码
点击排行
推荐阅读
最新文章
- SpringBoot2全家桶,快速入门学习开发网站教程
- Springboot2将连接池hikari替换为druid,体验最强大的数据库连接池
- CentOS7安装Docker,走上虚拟化容器引擎之路
- 2048小游戏-低调大师作品
- CentOS8,CentOS7,CentOS6编译安装Redis5.0.7
- CentOS7设置SWAP分区,小内存服务器的救世主
- Windows10,CentOS7,CentOS8安装Nodejs环境
- CentOS8编译安装MySQL8.0.19
- Linux系统CentOS6、CentOS7手动修改IP地址
- CentOS6,7,8上安装Nginx,支持https2.0的开启