【问题标题】:Why varargs should be the last in method signature?为什么可变参数应该是方法签名中的最后一个?
【发布时间】:2011-01-10 20:09:19
【问题描述】:

如果我尝试编写如下方法

public void someStuff(Object ... args, String a )

我收到此错误

方法someStuff的可变参数类型Object必须是最后一个参数。

我不完全理解变量参数类型是最后一个的要求。 任何输入都会有所帮助。

【问题讨论】:

  • 一般来说,答案是“因为那些是规则”。规矩就是规矩。为什么为什么规则存在很重要?你有什么问题?
  • @S.Lott:我同意,但我仍然对 Sun 的决定背后的理由感到好奇。
  • 每当我看到任何错误消息时,我都会觉得我做错了什么。除了违反规则之外,我在这里做错了什么?
  • 在这种情况下,答案恰好是“因为 C 是这样工作的”。这有帮助吗?或者您想了解 C 早期用于实现它的 C 编译器技巧的详细信息吗?

标签: java method-signature


【解决方案1】:

变量参数必须是最后一个,这样编译器才能确定哪个参数是哪个。

比如说你通过了

“测试”、“测试”、“测试”、“测试”

进入你的功能

public void someStuff(Object ... args, String a)

如果您希望 args 变量包含 3 或 4 个字符串,Java 无法解决。在撰写本文时,这对您来说可能是显而易见的,但它是模棱两可的。

但是,当它反过来时

public void someStuff(String a, Object ... args)

Java 编译器看到第一个字符串,将其粘贴到“a”中,然后知道剩余的字符串可以安全地放入 args 并且变量没有歧义。

【讨论】:

  • 理论上,技术上是可行的。技术问题仅在您使用 3 个或更多参数且 varargs 不是第一个或最后一个参数时才开始。
  • @Balus:即使varargs在中间也没有歧义,只是在编译器中实现的困难,但理论上是可能的。当您有多个可变参数项时,真正的问题就出现了。然后你必须有诸如回溯之类的事情来解决这意味着什么。它基本上变得像正则表达式匹配一样难以解决。
  • 会带来一个有趣的语言功能:pass-by-regexp。 void foo(^([0-9]*)(A|B.)(bar)?$)
  • 这不是模棱两可的。 String a 不是可选的,你不能跳过它。所以第四个"test" 必须是a。没有其他可能。
  • 如果将方法重载添加到方程中,在许多情况下可能会出现歧义
【解决方案2】:

考虑到如何使用带有 var args 的方法,任何其他格式都可能导致歧义。将可变参数放在最后可以防止可能的歧义,而无需额外的语法来解决歧义,这会降低该功能的好处。

考虑以下方法声明:

public void varargsAreCool(String surname, String firstname, 
                           String... nicknames) {
    // some cool varargs logic
}

当像varargsAreCool("John", "Smith") 这样使用时,很明显John Smith 没有昵称。当像这样使用varargsAreCool("Andrew", "Jones", "The Drew", "Jonesy").

现在考虑以下无效的方法声明:

public void varargsAreCool(String surname, String... nicknames,
                           String firstname) {
    // some cool varargs logic
}

varargsAreCool("John", "Smith") 使用时,Smith 是约翰的昵称还是他的姓氏?如果是他的姓,我怎么表示他没有昵称?要做到这一点,您可能必须使用像 varargsAreCool("John", new String[]{}, "Smith") 这样的方法,这种方法很笨拙,并且在某种程度上违背了该功能的目的。

varargsAreCool("Andrew", "The Drew", "Jonesy", "Jones") 这样使用时,The Drew, Jonesy and Jones 所有昵称和姓氏都不见了吗?同样,这种歧义可以得到解决,但代价是笨拙的附加语法。

【讨论】:

  • 通过声明所有非可变参数必须存在并且在参数列表中从左或右匹配,并且剩余的 0 个或多个参数组成可变参数,可以轻松解决歧义。在您的第二个示例中,确实很难说 John 没有昵称,但这只是糟糕的方法参数设计:如果可以预见参数列表可能为空,则不能将其放在中间。
【解决方案3】:

它遵循 C 约定。反过来,C 约定基于在堆栈上传递参数的 CPU 架构。第一个非可变参数最终在堆栈帧中的固定偏移量处结束。如果您可以将可变参数放在第一位,则以下参数的堆栈偏移量将取决于您将传递多少可变参数。这将使访问它们所需的代码量大大复杂化。

在您的示例中,首先使用String a,它在概念上位于偏移量0,与后面的可变参数数量无关。但是使用String a last,它可能位于偏移量 0、4、8、12 等处 - 您必须在每次需要 String a 时计算 args.size * 4

【讨论】:

  • 嗯..也许这就是@S.Lott 所指的。
  • 虽然稍微简化了一点(因此“概念上位于偏移量 0”)。实际上,您只有一个堆栈,它还保存寄存器溢出、返回地址等。
  • 是的。 ... 在 Java 中排在最后的原因是“因为这是规则”。 ... 在 C 中排在最后的原因是 C 编译器琐事的疯狂片段。 “为什么”没有帮助。规则就是。
  • 看来应该接受linead的答案作为正确答案。
  • @S.Lott 这不是“疯狂”,当然也不是“琐事”。这是一个完全理性的决定,取决于两者中的哪一个会产生更好的解决方案。
【解决方案4】:

因为这会使语言变得不必要地复杂。想象一下,如果您还允许其他语法:

public void someStuff(String a, Object ... args, String b)
{
}

甚至:

public void someStuff(String a, Object ... args, int b, Object ... args2)
{
}

第二个语法意味着一个字符串后跟任意数量的 Object 类型的参数,然后是一个整数,然后是更多的对象。当然,您可以设计一种可以接受此类内容的语言,但是如果您还想指定 args2 必须包含至少一个元素,但 args 可以为空怎么办?为什么我们也不能这样做?你可以设计这样一种语言。

归结为,您希望规则有多复杂?在这种情况下,他们选择了一个满足需求的简单选项。

【讨论】:

    【解决方案5】:

    String 也是 Object 的一个实例,所以如果你使用 varargs,你的 vararg 数组必须是最后一个参数,因为编译器无法真正决定什么是 args,什么是你的字符串 a。将方法调用视为方法名称的元组和作为参数的对象列表。如果你有两种这样的方法:

    public void someStuff(Object ... args, String a )
    public void someStuff(String a, String b)
    

    编译器无法决定为 someStuff("Hello", "Hello") 选择什么方法。 如果您将 String a 作为第一个参数,它可以确定 someStuff(String, String) 比 someStuff(String, Object) 更具体。

    【讨论】:

    • @Daff,我认为您的示例与可变参数无关。即使方法是:public void someStuff(Object args, String a),编译器仍然必须决定是使用:someStuff(Object, String) 还是 someStuff(String, String)。还是我在你的解释中遗漏了什么?
    • 嗯,好吧,我不知道我是否以最好的方式解释了它,我也没有声称这是正确的解释。基本上它主要是一个兼容性问题,因为 varargs 是后来在 java 语言中添加的,并且被视为一个数组。所以我的猜测是,他们不想更改查找算法来选择正确的方法来调用,而你不必这样做,当你可以确定只有最后一个参数可以是可变参数时。
    猜你喜欢
    • 2012-03-11
    • 2011-09-15
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-08-23
    • 1970-01-01
    相关资源
    最近更新 更多