【问题标题】:When do you use varargs in Java?你什么时候在 Java 中使用可变参数?
【发布时间】:2010-10-20 11:24:19
【问题描述】:

我害怕可变参数。我不知道用它们做什么。

另外,让人们随意传递尽可能多的参数感觉很危险。

什么是一个可以使用它们的好地方的上下文示例?

【问题讨论】:

  • 我不明白为什么它会是“危险的”。这并不比使用不同参数多次调用方法更危险。您对堆栈有顾虑吗?那么你不应该,因为可变参数被映射到通过引用传递的数组。
  • 只是想插话:避免您(尚未)完全适应的语言功能没有错。比使用您不了解的功能要好得多! ;)
  • 害怕未知是正常的。要了解更多关于可变参数的信息,请点击此处docs.oracle.com/javase/tutorial/java/javaOO/arguments.html。你会看到,可变参数没什么好怕的。可变参数很有用。了解如何使用 varargs 使您能够编写诸如 [PrintStream.format]("docs.oracle.com/javase/7/docs/api/java/io/…, java.lang.Object...)") 之类的方法:)。
  • 这不是对可变参数的批评,但实际上有一些“害怕”的理由(直到你完全理解可变参数的局限性);这就是存在 @SafeVarargs 注释的原因。 stackoverflow.com/a/14252221/1593924

标签: java variadic-functions


【解决方案1】:

可变参数对于任何需要处理不确定数量的对象的方法很有用。一个很好的例子是String.format。格式字符串可以接受任意数量的参数,因此需要一种机制来传入任意数量的对象。

String.format("This is an integer: %d", myInt);
String.format("This is an integer: %d and a string: %s", myInt, myString);

【讨论】:

  • 数组参数也可以接收不确定数量的对象,但可变参数参数在调用点允许更大的灵活性和便利性。您可以编写代码来构建一个数组并传递它,或者当您选择在可变参数参数中接收它时,您可以让 Java 为您完成。
  • 是否有一些令人信服的理由让您不只传递对某个 ADT 的引用,无论是简单的链表还是更成熟的 List 对象?当然,它仍然可以在堆栈上传递,只需在标题中将其设置为具有大小/计数字段的结构即可。可变参数看起来就像污染已传播到 C 系列中的所有语言。
【解决方案2】:

一个好的经验法则是:

“对需要 T 数组(无论 T 可能是什么类型)作为输入的任何方法(或构造函数)使用可变参数”。

这将使调用这些方法更容易(无需new T[]{...})。

您可以扩展此规则以包含带有List<T> 参数的方法,前提是此参数仅用于输入(即,该方法不修改列表)。

此外,我会避免使用 f(Object... args),因为它会滑向一种 API 不明确的编程方式。

示例方面,我在DesignGridLayout中使用过,可以一次调用添加多个JComponents:

layout.row().grid(new JLabel("Label")).add(field1, field2, field3);

在上面的代码中,add() 方法被定义为add(JComponent... components)

最后,这些方法的实现必须注意这样一个事实,即它可能被一个空的可变参数调用!如果您想强加至少一个论点,那么您必须使用一种丑陋的技巧,例如:

void f(T arg1, T... args) {...}

我认为这个技巧很难看,因为该方法的实现不如在其参数列表中仅包含 T... args 那么简单。

希望这有助于澄清关于可变参数的观点。

【讨论】:

  • 您是否考虑改为添加前置条件检查if (args.length == 0) throw new RuntimeException("foo");? (由于调用者违反了合同)
  • 好吧,一个好的 API 的目的是尽可能早地防止误用,所以在可能的编译时,因此建议 void f(T arg1, T... args) 始终确保它永远不会在没有参数的情况下被调用,无需等到运行时。
  • 我认为大多数时候调用一个没有参数的纯 varargs 函数就等于什么也不做。关键是不为可变参数函数提供参数很可能不会造成很大的危害。
  • 可变参数很有用,但它们并不等同于使用数组。 Varargs 是不可具体化的。因此,与泛型一样,它们会受到类型擦除的影响,这很可能是一个不可接受的约束,具体取决于工作。
【解决方案3】:

我经常使用可变参数输出到日志以进行调试。

我的应用程序中几乎每个类都有一个方法 debugPrint():

private void debugPrint(Object... msg) {
    for (Object item : msg) System.out.print(item);
    System.out.println();
}

然后,在类的方法中,我有如下调用:

debugPrint("for assignment ", hwId, ", student ", studentId, ", question ",
    serialNo, ", the grade is ", grade);

当我对我的代码正常工作感到满意时,我会在 debugPrint() 方法中注释掉代码,这样日志就不会包含太多无关的和不需要的信息,但我可以将单独的调用留给 debugPrint()未注释。稍后,如果我发现一个错误,我只需取消对 debugPrint() 代码的注释,然后我对 debugPrint() 的所有调用都会重新激活。

当然,我可以轻松地避开可变参数,而是执行以下操作:

private void debugPrint(String msg) {
    System.out.println(msg);
}

debugPrint("for assignment " + hwId + ", student " + studentId + ", question "
    + serialNo + ", the grade is " + grade);

然而,在这种情况下,当我注释掉 debugPrint() 代码时,服务器仍然不得不在每次调用 debugPrint() 时连接所有变量,即使没有对结果字符串做任何事情.但是,如果我使用可变参数,服务器只需将它们放在一个数组中,然后它就会意识到它不需要它们。节省了大量时间。

【讨论】:

  • 您可以在超类中实现一个调试方法来使用反射打印任何对象。
【解决方案4】:

当我们不确定要在方法中传递的参数数量时,可以使用可变参数。它在后台创建一个未指定长度的参数数组,这样的参数在运行时可以被视为一个数组。

如果我们有一个方法被重载以接受不同数量的参数,那么我们可以简单地使用可变参数概念,而不是多次重载该方法。

此外,当参数的类型发生变化时,使用“Object...test”将大大简化代码。

例如:

public int calculate(int...list) {
    int sum = 0;
    for (int item : list) {
        sum += item;
    }
    return sum;
}

这里间接地将一个int类型的数组(list)作为参数传递,并在代码中被视为一个数组。

为了更好地理解,请点击这个链接(它对我清楚地理解这个概念有很大帮助): http://www.javadb.com/using-varargs-in-java

P.S:当我不知道它时,即使我也害怕使用可变参数。但现在我已经习惯了。 俗话说:“我们执着于已知,害怕未知”,所以尽可能多地使用它,你也会开始喜欢它:)

【讨论】:

  • C# 等价的可变参数是“params”。它也做同样的事情并接受可变数量的参数。请参阅此内容以获得更好的理解:dotnetperls.com/params
【解决方案5】:

可变参数是 java 1.5 版本中添加的功能。

为什么要使用这个?

  1. 如果您不知道要为方法传递的参数数量怎么办?
  2. 如果您想向方法传递无限数量的参数怎么办?

这是如何工作的?

它使用给定的参数创建一个数组并将该数组传递给方法。

例子:

public class Solution {



    public static void main(String[] args) {
        add(5,7);
        add(5,7,9);
    }

    public static void add(int... s){
        System.out.println(s.length);
        int sum=0;
        for(int num:s)
            sum=sum+num;
        System.out.println("sum is "+sum );
    }

}

输出:

2

总和是 12

3

总和是 21

【讨论】:

    【解决方案6】:

    我也有与可变参数相关的恐惧:

    如果调用者将显式数组传递给方法(而不是多个参数),您将收到对该数组的共享引用。

    如果您需要在内部存储此数组,您可能需要先克隆它以避免调用者稍后更改它。

     Object[] args = new Object[] { 1, 2, 3} ;
    
     varArgMethod(args);  // not varArgMethod(1,2,3);
    
     args[2] = "something else";  // this could have unexpected side-effects
    

    虽然这与传入任何类型的对象(其状态稍后可能会更改)并没有真正的不同,因为数组通常是(在使用多个参数而不是数组的调用的情况下)由编译器在内部创建的新数组你可以放心使用,这肯定是意想不到的行为。

    【讨论】:

    • 正确,但这个例子似乎有点牵强。人们是否会以这种方式使用您的 API?
    • 你永远不知道...尤其是当参数的数量没有在调用端硬编码,而且还在循环中收集到一个列表中时,传入一个数组并不是全部不常见。
    • 我认为您的用例示例一般来说并非不可能。有人可能会争辩说,您的 API 不应该以这种方式使用输入数组,如果使用了,它必须记录它。
    • 重载方法支持两者; argMethod(Object.. objList) argMethod(Object[] objList)
    • @thetoolman:我认为这是不可能的。会被视为重复方法。
    【解决方案7】:

    我经常将可变参数用于可以采用某种过滤器对象的构造函数。例如,我们基于 Hadoop 的系统的很大一部分是基于一个 Mapper,它处理项到 JSON 的序列化和反序列化,并应用多个处理器,每个处理器获取一个内容项并修改并返回它,或者返回 null拒绝。

    【讨论】:

      【解决方案8】:

      在 Var-Args 的 Java 文档中,var args 的用法非常清楚:

      http://docs.oracle.com/javase/1.5.0/docs/guide/language/varargs.html

      关于用法它说:

      “那么什么时候应该使用可变参数? 作为客户端,只要 API 提供它们,您就应该利用它们。核心 API 中的重要用途包括反射、消息格式化和新的 printf 工具。 作为 API 设计人员,您应该谨慎使用它们,只有当其好处真正令人信服时。 一般来说,您不应该重载可变参数方法,否则程序员将很难弄清楚调用了哪个重载。 "

      【讨论】:

        猜你喜欢
        • 2017-12-06
        • 1970-01-01
        • 2015-03-04
        • 2010-09-19
        • 2012-04-23
        • 2018-01-17
        • 2010-09-10
        • 1970-01-01
        相关资源
        最近更新 更多