Java的朋友Kotlin:默认参数


Kotlin函数和构造函数可以定义默认参数,从而允许对其的调用跳过具有默认值的任何参数。

这允许以多种方式调用以下函数:

fun doStuff(
  a: String = "Default value",
  b: Int = 1,
  c: Boolean = false
)
doStuff()
doStuff("here's a value")
doStuff("here's a value", 2)
doStuff("here's a value", 2, true)
doStuff("here's a value", c = true)
doStuff(b = 2)
doStuff(c = true)
doStuff(b = 2, c = true)

可以在Kotlin文档中找到更多信息。

在本文的其余部分,我们将研究如何在API中包括默认参数,同时仍提供出色的Java兼容性。

试图调用不友好的Kotlin函数 用Java的默认参数调用函数或构造函数将不会花费很多时间。从Java调用前面显示的函数的唯一方法是提供它要求的每个参数:

doStuff("here's a value", 2, true);

没有一些帮助,Java就无法理解默认参数的概念。

应用@JvmOverloads批注 该@JvmOverloads注解可以应用到的功能和构造来告诉编译器生成取决于它们的默认参数额外的过载。重载位于编译后的字节码中。然后,您可以像其他任何函数或构造函数一样从Java执行它们。

让我们看一下KDoc,@JvmOverloads其中有一个关于如何生成重载的精确声明:

/**
 * Instructs the Kotlin compiler to generate overloads for this function that 
 * substitute default parameter values.
 *
 * If a method has N parameters and M of which have default values, M overloads 
 * are generated: the first one takes N-1 parameters (all but the last one that 
 * takes a default value), the second takes N-2 parameters, and so on.
 */
@Target(AnnotationTarget.FUNCTION, AnnotationTarget.CONSTRUCTOR)
@Retention(AnnotationRetention.BINARY)
@MustBeDocumented
public actual annotation class JvmOverloads

换一种说法:

从最后一个参数开始。 如果参数具有默认值,则将生成不带该参数的重载,该重载将在其实现中使用默认值。 如果倒数第二个参数具有默认值,则会生成没有最后两个参数的重载。 继续应用此逻辑,直到到达第一个参数或命中没有默认值的参数为止。 一些例子应该有助于理解这个定义。

下面是doStuff从Java早期版本调用函数的不同方法:

// All arguments have default values
@JvmOverloads
fun doStuff(
  a: String = "Default value",
  b: Int = 1,
  c: Boolean = false
)
// Ways to call [doStuff] from Java
doStuff();
doStuff("here's a value");
doStuff("here's a value", 2);
doStuff("here's a value", 2, true);

Java现在有四个选项可以触发(而不是没有注释的1个)。所有的参数都被一个接一个地删除,留下了3个重载,其中1个根本不需要输入。

我们删除其中一个默认值,然后生成的选项将更改:

// Remove default value from [b]
@JvmOverloads
fun doStuff(
  a: String = "Default value",
  b: Int,
  c: Boolean = false
)
// Ways to call [doStuff] from Java
doStuff("here's a value", 2);
doStuff("here's a value", 2, true);

现在,当创建重载时,编译器将命中b(第二个参数)并停止。a具有默认值无关紧要,编译器不会从这一点开始。因此,Java仅可使用一个额外的重载。

将注释应用于类的构造函数需要对其结构进行一些轻微的操作:

class MyJvmOverloadsClass @JvmOverloads constructor(
  private val a: String = "Default value",
  private val b: Int = 1,
  private val c: Boolean = false
) {

  // Rest of the class
}

注释必须直接应用于类的构造函数之一。在这种情况下,由于只有一个构造函数,因此constructor必须添加关键字(通常可以在Kotlin类中省略该关键字)。应用注释会产生重载,其方式与函数相同。

注释不完美 @JvmOverloads注释的问题在于,它不会生成参数的所有可能组合。Kotlin可以绕过此操作,因为它可以访问命名参数。

Java没有此功能。即使使用了@JvmOverloads注释,这也阻止了Java访问与Kotlin相同的所有选项。

当您考虑它时,这是有道理的。

例如,仅当函数或构造函数具有与所有其他版本不同的参数时,才可以重载(以Kotlin编写,但Java遵循相同的规则):

fun doStuff(a: String = "Default value"): String
fun doStuff(a: String = "Default value", b: Int = 1): String
fun doStuff(a: String = "Default value", b: Int = 1, c: Boolean = false): String
// Return type can be different as long as it has different arguments
fun doStuff(a: String = "Default value", b: Int = 1, c: Boolean = false, d: Long = 2): Int

由于参数类型不同,因此每个重载之间的区别很明显。但是,如果它们都是相同的类型,则将变得更难使用:

fun doStuff(a: String = "Default value"): String
fun doStuff(a: String = "Default value", b: String = "Another value"): String
fun doStuff(a: String = "Default value", b: String = "Another value", c: String = "Another one"): String

命名的参数使得这款可以忍受的(假设它们被命名为好过a,b和c)。由于默认值,您可以省略一些参数。

如果不使用命名参数,编译器将无法在传递给该函数的对象之间进行区分。因此,它所能做的就是依次将它们依次传递到函数中。这是@JvmOverloads面临的问题。它无法生成所有可能的迭代,因为没有足够的信息来区分过载。如果所有参数都具有不同的类型,那么从技术上讲,编译器可以做到这一点,但是松散地应用规则会造成混淆。

这就是@JvmOverloads仅从最后一个参数(该参数也必须具有默认值)开始生成重载,然后移至下一个参数(向后指向第一个参数)的原因。

与注释合作 您可以解决的限制@JmvOverloads。这样做需要您考虑如何使用函数以及经常使用哪些参数组合。

以下是编写带注释的函数时要考虑的几点@JvmOverloads

  1. 按照重要性的顺序对参数进行排序,第一个参数是最重要的,然后随着它们的进行而递减。
@JvmOverloads
fun doStuff(
  superImportant: String = "I need this argument!",
  reallyImportant: Int = 1,
  somewhatImportant: Boolean = false,
  dontReallyCareAboutThisOne: Long = 2
)
  1. 不要将带有默认值的参数与没有默认值的参数混合在一起(更易于理解示例)。
// Stops generating overloads when [doesNotHaveDefaultValue] is reached
@JvmOverloads
fun doStuff(
  hasDefaultValue: String = "I need this argument!",
  doesNotHaveDefaultValue: Int,
  hasDefaultValue2: Boolean = false,
  hasDefaultValue3: Long = 2
)

// No overloads can be generated
@JvmOverloads
fun doStuff(
  hasDefaultValue: String = "I need this argument!",
  doesNotHaveDefaultValue: Int,
  hasDefaultValue2: Boolean = false,
  doesNotHaveDefaultValue2: Long
)
  1. 如果仍然缺少您认为有用的组合,请手动创建重载。
// Original
@JvmOverloads
fun doStuff(
  a: String = "I need this argument!",
  b: Int = 1,
  c: Boolean = false,
  d: Long = 2
)

// Calling with just [a] and [c] is useful
fun doStuff(a: String, c: Boolean) {
  // Add in the missing parameters
  // Copy the default values or maybe they are taken from properties of a class
  // instead of being passed into the function
  return doStuff(a, 1, c, 2)
}

我想在这里给您的主要建议是真正考虑您创建的功能。当创建公共API作为库的一部分时,尤其是当您希望它与Java完美结合时,花时间考虑如何利用您的函数将使每个人都更加高兴。

使用Kotlin API的开发人员自己使用该语言时,可以使用其功能来解决代码中的潜在问题。另一方面,Java要求您花一些脑力来编写易于使用的函数。

概括 将@JvmOverloads注解添加到函数和构造函数中,可以说服它们更好地与Java配合使用。它通过生成其他重载并将其放置在Java与之交互的字节码中来实现。添加注释并不总是可以使Java轻松访问您的API,在这种情况下,您需要自己投入工作并精心设计一个深思熟虑的API。


原文链接:http://codingdict.com