Java一个方法中的两个可变参数

46

在Java中有没有办法创建一个期望两个不同varargs的方法?我知道,对于相同的对象类型来说这是不可能的,因为编译器不知道从哪里开始或结束。但是为什么使用两种不同的Object类型也不可能呢?

例如:

public void doSomething(String... s, int... i){
    //...
    //...
}

有没有办法创建这样的方法?

谢谢!


4
你不能这样做:编译器怎么知道第一个子列表在哪里结束,第二个子列表从哪里开始呢? - Sergey Kalinichenko
4
我很好奇这个用法是什么意思。 - Drew McGowen
2
@dasblinkenlight 一组参数是String类型,而另一组参数是int类型;编译器可以推断出第一个参数何时结束以及第二个参数何时开始,对吗? - arshajii
2
@arshajii - 在这种情况下是的,但并不是普遍情况。如果您有public void doSomething(A… as, B… bs),而BA的子类型呢?另一方面,Scala 通过允许在一个方法上使用多个参数列表来支持此功能:def doSomething(as: A*)(bs: B*) - DaoWen
与编译器选择最符合对象的重载方式类似(给定一个接受Object和一个接受Object子类的重载,如果可能它将使用接受子类的方法),编译器可以用相同方式确定可变参数的截止点。 - Cruncher
显示剩余4条评论
12个回答

44

只能接受一个可变参数,很抱歉。但使用asList()函数可以让它变得近乎方便:

 public void myMethod(List<Integer> args1, List<Integer> args2) {
   ...
 }

 -----------

 import static java.util.Arrays.asList;
 myMethod(asList(1,2,3), asList(4,5,6));

我不喜欢这个,因为对args1或args2的任何处理都需要进行强制类型转换。很混乱。 - William Morrison
如果您知道通用类型,请使用它 - 那么就不需要转换。上面的示例将“等效于”方法(Object…):myMethod(List args1, List args2)在上面的示例中,可以无需转换即可正常工作。 - rec
2
@WilliamMorrison - 如果您使用实际引用类型(例如Integer)而不是<?>,则不需要进行强制转换。 - DaoWen
@DaoWen 也许应该这样写?还是我漏掉了什么?它不是一个泛型方法... - William Morrison
我还必须导入 java.util.List; 才能使其工作。奇怪的是,Eclipse 不会自动导入 java.util.Arrays.asList;。以防对任何人有所帮助,我想指出 args1.get(1) 是获取 List 成员的方法。 - WVrock

19

在Java中,只允许有一个varargs参数,并且它必须是签名的最后一个参数。

但它只是将其转换为数组,所以你应该只将你的两个参数明确声明为数组:

public void doSomething(String[] s, int[] i){

谢谢! 是的,没错,但在我的情况下,两个固定数组不太好。 - T_01
2
方法签名不固定数组的大小;调用者可以传入任何大小的数组。 - rgettman
方便的调用方式是:doSomething(new String[] {"a", "b"}, new int[] {1,2}). 可以做到。这里的好处是,在int情况下,不会像使用asList()变体一样自动装箱为Integer。但是,个人更喜欢写法较少的asList()变量。 - rec

11
一个可能的API设计,其中调用代码看起来像:

A possible API design in which the calling code looks like

    doSomething("a", "b").with(1,2);

通过“流畅”的API

public Intermediary doSomething(String... strings)
{
    return new Intermediary(strings);
}

class Intermediary
{
    ...
    public void with(int... ints)
    {
        reallyDoSomething(strings, ints);
    }
}

void reallyDoSomething(String[] strings, int[] ints)
{
    ...
}

危险在于程序员忘记调用 with(...)

    doSomething("a", "b");  // nothing is done

也许这样会更好一些

    with("a", "b").and(1, 2).doSomething();

5

只允许使用一个 vararg 参数。这是因为多个相同类型的 vararg 参数会产生歧义。例如,如果传入两个相同类别的 varargs,将会产生不确定性。

public void doSomething(String...args1, String...args2);

args1在哪里结束,args2在哪里开始?或者说这里有更加令人困惑的内容。
class SuperClass{}
class ChildClass extends SuperClass{}
public void doSomething(SuperClass...args1, ChildClass...args2);

ChildClass继承自SuperClass,因此它可以合法地存在于args1或args2中。这种混淆是为什么只允许一个 varargs 的原因。

varargs 还必须出现在方法声明的末尾。

只需声明特定类型而不是2个数组即可。


好的,谢谢!但是我明白同一类型的两个可变参数没有意义 ^^ - T_01
@T_01 我有一个例子说明为什么使用2种不同的参数类型是不可能的。即使这些类没有共享类型,编译器也无法处理,因为它并不够聪明。 - William Morrison

4
虽然这种方法偶尔有用,但通常如果你发现在Java中遇到了限制,你可能可以重新设计一些东西并取得更好的效果。以下是一些可能的其他看法...
如果这两个列表有任何关联,您可能想要为两个不同的列表创建一个包装器类,并传递包装器。几乎总是建议在集合周围使用包装器--它们为您提供了一个添加与集合相关的代码的地方。
如果这是一种初始化数据的方法,请从字符串解析它。例如,“abc,123:def,456:jhi,789”几乎可以轻松地通过2个split语句和一个循环(2-3行代码)分割。您甚至可以制作一个小型自定义解析器类,将这样的字符串解析成一个结构,然后将其馈入您的方法。
嗯--老实说,除了初始化数据之外,我甚至不知道为什么你会想要这样做,在任何其他情况下,我希望您会传递2个集合,并且根本不会对varags感兴趣。

2
你可以这样做,然后你可以在该方法内部进行转换并添加其他逻辑。
public void doSomething(Object... stringOrIntValues) {
    ...
    ...
}

然后像这样使用这种方法:

doSomething(stringValue1, stringValue2, intValue1, intValue2,         
    intValue3);

1

这是一个旧的帖子,但我认为这仍然会有所帮助。

我找到的解决方案不是很优雅,但它可以工作。我创建了一个独立的类来处理繁重的工作。它只有我需要的两个变量及其getter方法。构造函数自行处理设置方法。

我需要传递方向对象和相应的数据对象。这也解决了可能出现的不对称数据对问题,但这可能只适用于我的使用需求。

public class DataDirectionPair{

    Data dat;
    Directions dir;

    public DataDirectionPair(Data dat, Directions dir) {
        super();
        this.dat = dat;
        this.dir = dir;
    }

    /**
     * @return the node
     */
    public Node getNode() {
        return node;
    }

    /**
     * @return the direction
     */
    public Directions getDir() {
        return dir;
    }
}

我将把此类作为方法的变长参数传递。
public void method(DataDirectionPair... ndPair){
    for(DataDirectionPair temp : ndPair){
        this.node = temp.getNode();
        this.direction = temp.getDir();
        //or use it however you want
    }
}

1
由于 Java 语言规范(请参见8.4.1. Formal Parameters)的规定,这是不可能的:

方法或构造函数的最后一个形式参数是特殊的:它可以是可变元素参数,由类型后面的省略号表示。

请注意,省略号(...)是一个独立的标记(§3.11)。虽然可以在它和类型之间放置空格,但这种做法不被推荐。

如果最后一个形式参数是可变元素参数,则该方法是可变元素方法。否则,它是固定元素方法。

关于为什么只有最后一个参数可以使用可变参数,这只是猜测,但可能是因为允许这样做可能会导致不可决或模棱两可的问题(例如考虑 method(String... strings, Object... objects) 会发生什么),而只允许非交叉类型会导致复杂性(例如考虑重构,以前非交叉类型突然变成交叉类型),不清楚何时适用或不适用,编译器决定时的复杂性。

我想知道为什么有人会回答一个已经有被接受的答案且超过4年的问题.. ^^ 但无论如何还是谢谢你。 - T_01
@T_01 当我正在回答另一个问题时,它被关闭为这个问题的重复,所以我决定不让它浪费并在这里发布它。 - Mark Rotteveel

0

如果大多数情况下您不会传递大量字符串作为第一个参数,那么您可以提供一堆重载方法,这些方法接受不同数量的字符串并将它们包装在数组中,然后调用以该数组作为第一个参数的方法。

public void doSomething(int... i){
    doSomething(new String[0], i);
}
public void doSomething(String s, int... i){
    doSomething(new String[]{ s }, i);
}
public void doSomething(String s1, String s2, int... i){
    doSomething(new String[]{ s1, s2 }, i);
}
public void doSomething(String s1, String s2, String s3, int... i){
    doSomething(new String[]{ s1, s2, s3 }, i);
}
public void doSomething(String[] s, int... i) {
    // ...
    // ...
}

0

你可以将你的可变参数转换为数组

public void doSomething(String[] s, int[] i) {
    ...
}

然后使用一些帮助方法将您的可变参数转换为数组,如下所示:

public static int[] intsAsArray(int... ints) {
    return ints;
}

public static <T> T[] asArray(T... ts) {
    return ts;
}

然后,您可以使用这些辅助方法来转换您的可变参数。

doSomething(asArray("a", "b", "c", "d"), intsAsArray(1, 2, 3));

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接