乐趣区

关于android: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 一个合成办法 —— 它有三个参数: ToyintObject。只有是应用了默认参数就会被调用。三个参数中的 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 != 0true 所以 var0 = goodDoggo
  • var3 & 2 != 0false 所以 var1 没有被替换
  • var3 & 4 != 0true 所以 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 系列 的其余文章。

退出移动版