Kotlin默认参数基本用法、构造函数、与 Java 代码相互调用
-
默认参数
如果您需要重载一个函数,您可以使用默认参数,而不是将同一个函数实现许多次:
// 无需像下面这样实现:
fun play(toy: Toy){ ... }
fun play(){
play(SqueakyToy)
}
// 使用默认参数:
fun play(toy: Toy = SqueakyToy)
fun startPlaying() {
play(toy = Stick)
play() // toy = SqueakyToy
}
class Doggo(
val name: String,
val rating: Int = 11
)
val goodDoggo = Doggo(name = "Tofu")
val veryGoodDoggo = Doggo(name = "Tofu", rating = 12)
// kotlin
fun play(toy: Toy = SqueakyToy) {... }
// java
(());
(); // error: Cannot resolve method 'play()'
/* Copyright 2020 Google LLC.
SPDX-License-Identifier: Apache-2.0 */
fun play(toy: Toy = SqueakyToy) {… }
-
@JvmOverloads
/* 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( Toy toy) {
(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( Doggo doggo, Doggo doggo2, 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);
}
-
var3 & 1 != 0 是 true 所以 var0 = goodDoggo -
var3 & 2 != 0 是 false 所以 var1 没有被替换 -
var3 & 4 != 0 是 true 所以 var2 = SqueakyToy
通过对 var3 应用位掩码,编译器可以计算出哪个参数应当被替换为默认值。
-
按位与操作 https://en.wikipedia.org/wiki/Bitwise_operation#AND
您也许会注意到,在上面的例子中 Object 参数的值始终为 null,但在 play$default 函数中从未被用到过。该参数与支持重载函数中的默认值有关。
当我们想要覆盖某个使用了默认参数的函数时会发生什么呢?
-
将 play 函数改为 Doggo 类型的 open 函数,并将 Doggo 改为 open 类型。 -
创建一个新的类型: PlayfulDoggo,该类型继承 Doggo 并覆盖 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 并检查反编译的代码,() 函数会变得如下列代码这样:
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 = ();
}
(var1);
}
}
/* 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( String name, int rating) {
(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
就像主构造函数一样,拥有默认参数的次级构造函数也会生成一个使用 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( 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( 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);
}
}
THE END