【问题标题】:Java varargs method param list vs. arrayJava varargs 方法参数列表与数组
【发布时间】:2011-07-21 07:29:00
【问题描述】:

可变参数:

public static void foo(String... string_array) { ... }

单个数组参数:

public static void bar(String[] string_array) { ... }

Java 1.6 似乎接受/拒绝以下内容:

String[] arr = {"abc", "def", "ghi"};
foo(arr);  // accept
bar(arr);  // accept
foo("abc", "def", "ghi");  // accept
bar("abc", "def", "ghi");  // reject

假设上述是正确/正确的,为什么不总是使用可变参数而不是单个数组参数?似乎免费添加了一点调用者的灵活性。

专家能否分享一下 JVM 内部的差异,如果有的话?

谢谢。

【问题讨论】:

    标签: java variadic-functions


    【解决方案1】:

    另一个区别是效率。不会调用显式数组中的对象。然而,当方法被压入堆栈时,变量参数列表的参数会被评估。

    当函数调用作为返回变量参数列表中使用的类型的参数传递时,这一点很明显。

    示例: someMethod(对象... x) 另一个方法(对象[]);

    someMethod(a(), b(), c()); // a、b 和 c 将在你进入方法之前被调用。

    另一个方法(新对象[]{a(), b(), c()}); // 在访问对象之前不会调用方法。

    【讨论】:

    • The methods aren't invoked until the objects are accessed:令人惊讶!这是否意味着如果我不访问方法中的对象,它们就永远不会被调用?另外,您是否编译并运行此代码以确认行为?
    • 我给出的示例中没有调用对象参数的原因是因为它们嵌入在对象 [] 数组中。它们将在处理数组成员时被调用,但不会在被压入堆栈以进行原始方法调用时调用。
    • 也许我错了,但我认为这是不正确的:Java 是一种热切评估的语言。例如看一下this 代码。 (tio.run link)
    【解决方案2】:

    数组从 Java 开始就已经存在,而可变参数是最近才加入的。因此,许多旧代码仍然乐于使用数组。

    另请注意,使用显式数组参数调用泛型可变参数方法可能会默默地产生与预期不同的行为:

    public <T> void foo(T... params) { ... }
    
    int[] arr = {1, 2, 3};
    
    foo(arr); // passes an int[][] array containing a single int[] element
    

    因此 - 除了需要付出很多努力而没有明显的好处之外 - 用可变参数替换旧数组参数并不总是可取的。

    不能的情况就更不用说了,因为方法参数列表中的数组后面还有一个参数:

    public void foo(String[] strings, String anotherParam) { ... }
    

    重新排序参数可能会在技术上解决这个问题,但它会破坏客户端代码。

    更新:Java 2nd 生效。版本,第 42 条:明智地使用可变参数更详细地解释了这一点,还给出了一个具体的例子:Arrays.asList() 在 Java5 中被改造为具有可变参数,无意中破坏了许多现有代码 在使用这个(现已过时的)习语打印数组时可能会引起意外:

    System.out.println(Arrays.asList(myArray));
    

    更新 2: 仔细检查了来源,它说问题出现在原始类型数组上,例如 int[]。在可变参数之前,代码如下:

    int[] digits = { 3, 1, 4, 1, 5, 9, 2, 6, 5, 4 };
    System.out.println(Arrays.asList(digits));
    

    会产生编译错误,因为只有引用类型的数组可以转换为List。由于可变参数和改造asList,上面的代码编译时没有警告,意外的结果类似于"[[I@3e25a5]"

    【讨论】:

    • “传递一个包含单个 String[] 元素的 String[][] 数组”无法重现此
    • 为了澄清,您可以在签名中包含另一个不采用可变参数的参数——但 the vararg-accepting parameter has to go last。此外,在上面的示例中,调用foo(arr); 会使编译器生成一个包含您传入的String[]String[]。换句话说,它会生成一个二维String 数组,即String[][] .
    • 完全错误。将非原始数组传递给可变参数参数不会导致二维数组!解释了here 并易于测试。
    • @JoeCoder,并非完全错误,只是不精确。请查看我的更新。
    【解决方案3】:

    可变参数是数组的简单语法糖。

    如果你打电话给foo("abc", "def", "ghi"); 那么 编译器会将其称为foo(new String[] {"abc", "def", "ghi"});

    编译器将创建一个新数组并将其传递给foo()。 不能同时拥有foo(String...)foo(String[])。因为两者功能相同。

    【讨论】:

      【解决方案4】:

      不将所有内容都指定为可变参数的主要原因是它并不总是有意义。例如,如果 InputStream.read(byte[]) 定义为 `read(byte...) 则以下调用将是有效的:

      myInputStream.read(0, 1, 2, 3);
      

      这将创建一个 4 元素字节数组,将其传入然后丢弃。

      【讨论】:

        【解决方案5】:

        这就是可变参数的定义方式。 varargs 扩展不会使每个接受函数的数组都成为 varargs 函数。你必须像这样调用 bar:

        bar(new String[]{"abc", "def", "ghi"})
        

        【讨论】:

          【解决方案6】:

          在 foo 中指定三个参数, 您必须像这样调用 bar:

           bar(new String[]{"abc", "def", "ghi"});
          

          所以你只用一个参数来调用它,那就是 String[] 在这种情况下,这几乎与内部无关,您对方法 bar 的方法签名只是声明它只有一个参数,而 foo 有 n 个参数,它们都是字符串

          【讨论】:

            猜你喜欢
            • 2011-11-24
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2012-04-09
            • 1970-01-01
            • 1970-01-01
            相关资源
            最近更新 更多