45

Is there any way in Java to create a method, which is expecting two different varargs? I know, with the same object kind it isn't possible because the compiler doesn't know where to start or to end. But why it also isn't possible with two different Object types?

For example:

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

Is there any way to create a method like this?

Thank you!

Amal K
  • 4,359
  • 2
  • 22
  • 44
T_01
  • 1,256
  • 3
  • 16
  • 35
  • 4
    You cannot do that: how is the compiler to know where the first sublist ends, and the second one starts? – Sergey Kalinichenko Jul 25 '13 at 20:24
  • 4
    I'm curious as to the usage for this – Drew McGowen Jul 25 '13 at 20:25
  • 2
    @dasblinkenlight One set of args are of type `String` while the other are of type `int`; the compiler could infer where the first ends and the second begins, right? – arshajii Jul 25 '13 at 20:36
  • 2
    @arshajii - In this case yes, but not in the general case. What if you had `public void doSomething(A... as, B... bs)` and `B` is a subtype of `A`? On another note, [Scala](http://www.scala-lang.org/) _does_ support this by allowing multiple parameter lists on one method: `def doSomething(as: A*)(bs: B*)` – DaoWen Jul 25 '13 at 20:40
  • @arshajii: That was what confused me. – T_01 Jul 25 '13 at 20:42
  • What is your use case for trying to do this? – DaoWen Jul 25 '13 at 20:50
  • I have a method which is returning me an array of images from a specific package. For this i need multiple variables like scale width / height, scale type, a start and a break -point. But sometimes i want to resize and to have an breakpoint, sometimes just an breakpoint without scaling. So i wanted to set two varargs for this. But i solved it by excpeting an Integer, and just set it to "null" when i dont need this. But anyway i thougt it would be an interesting question :) – T_01 Jul 25 '13 at 20:56
  • @DaoWen I am aware of that Scala feature. But in any case, the compiler could easily emit a warning whenever there is a situation like the one you describe. I imagine that more often than not the situation will not be like that. Maybe it's an unnecessary feature, but I think it could be cool. – arshajii Jul 25 '13 at 21:59
  • @DaoWen Similar to how the compiler picks the overload that is most specific to an object (Given an overload that accepts and Object, and one that accepts a subclass of Object, it will go to the method that accepts the subclass if possible), the compiler could do that for determining the cut off point between varargs – Cruncher Sep 19 '13 at 13:09

12 Answers12

43

Only one vararg, sorry. But using asList() makes it almost as convenient:

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

 -----------

 import static java.util.Arrays.asList;
 myMethod(asList(1,2,3), asList(4,5,6));
rec
  • 10,340
  • 3
  • 29
  • 43
  • I dislike this as any processing on args1 or args2 would require casting. Messy. – William Morrison Jul 25 '13 at 20:30
  • If you know the generic type, use it - then there is no casting. The example above would be "equivalent" to method(Object…): myMethod(List args1, List args2) would work without casting in the example above. – rec Jul 25 '13 at 20:31
  • 2
    @WilliamMorrison - It doesn't require casting if you use an actual reference type (e.g. `Integer`) instead of `>`. – DaoWen Jul 25 '13 at 20:32
  • @DaoWen Maybe it should be written like that then? Or am I missing something here? Its not a genericized method... – William Morrison Jul 25 '13 at 20:34
  • I had to also import `java.util.List;` to make it work. What is weird is eclipse does not auto import `java.util.Arrays.asList;`. Just in case this helps anyone, I want to point out that `args1.get(1)` is the way to get the members of the List. – WVrock Nov 11 '14 at 04:59
19

In Java, only one varargs argument is allowed and it must be the last parameter of the signature.

But all it does it convert it to an array anyway, so you should just make your two parameters explicit arrays:

public void doSomething(String[] s, int[] i){
rgettman
  • 176,041
  • 30
  • 275
  • 357
  • Thank you! Yes thats right, but two fixed arrays arent good in my case. – T_01 Jul 25 '13 at 20:39
  • 2
    The method signature does not fix the size of the array; callers can pass in any size arrays they want. – rgettman Jul 25 '13 at 20:40
  • The convenient way to call this one is: doSomething(new String[] {"a", "b"}, new int[] {1,2}). Doable. The benefit here is that in the int case, no the int stays int and is not auto-boxed to Integer as in the variant using asList(). Still, personally I prefer the asList() variant which is less to write. – rec Jul 25 '13 at 20:50
11

A possible API design in which the calling code looks like

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

through "fluent" 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)
{
    ...
}

The danger is if the programmer forgot to call with(...)

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

Maybe this is a little better

    with("a", "b").and(1, 2).doSomething();
ZhongYu
  • 19,446
  • 5
  • 33
  • 61
5

Only one vararg is allowed. This is because multiple vararg arguments are ambiguous. For example, what if you passed in two varargs of the same class?

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

Where does args1 end and args2 begin? Or how about something more confusing here.

class SuperClass{}
class ChildClass extends SuperClass{}
public void doSomething(SuperClass...args1, ChildClass...args2);

ChildClass extends SuperClass, and so is can legally exist in args1, or args2. This confusion is why only one varargs is allowed.

varargs must also appear at the end of a method declaration.

Just declare the specific type instead as 2 arrays.

William Morrison
  • 10,953
  • 2
  • 31
  • 48
  • okay thank you! But i understood 2 varargs of the same type doesnt make sense ^^ – T_01 Jul 25 '13 at 20:37
  • @T_01 I've got an example of why its not possible with 2 different argument types. The compiler just isn't smart enough to handle that, even if the classes don't share types. – William Morrison Jul 25 '13 at 20:38
4

Although this kind of thing is occasionally useful, usually if you find that you are hitting a restriction in Java you could probably redesign something and come out much better. Here are some possible other ways to look at it...

If the two lists are related at all you probably want to create a wrapper class for the two different lists and pass in the wrapper. Wrappers around collections are almost always a good idea--they give you a place to add code that relates to the collection.

If this is a way to initialize data, parse it from a string. For instance, "abc, 123:def, 456:jhi,789" is almost embarassingly easy to split up with 2 split statements and a loop (2-3 lines of code). You can even make a little custom parser class that parses a string like that into a structure you feed into your method.

Hmm--honestly asside from initializing data I don't even know why you'd want to do this anyway, any other case and I expect you'd be passing in 2 collections and wouldn't be interested in varags at all.

Bill K
  • 62,186
  • 18
  • 105
  • 157
2

You can do something like this, then you can cast and add additional logic inside that method.

public void doSomething(Object... stringOrIntValues) {
    ...
    ...
}

And use this method like so:

doSomething(stringValue1, stringValue2, intValue1, intValue2,         
    intValue3);
LEMUEL ADANE
  • 8,336
  • 16
  • 58
  • 72
1

This is an old thread, but I thought this would be helpful regardless.

The solution I found isn't very neat but it works. I created a separate class to handle the heavy lifting. It only has the two variables I needed and their getters. The constructor handles the set methods on its own.

I needed to pass direction objects and a respective Data object. This also solves the possible problem of uneven data pairs, but that is probably only for my usage needs.

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;
    }
}

I would then just pass this class as the vararg for the method

public void method(DataDirectionPair... ndPair){
    for(DataDirectionPair temp : ndPair){
        this.node = temp.getNode();
        this.direction = temp.getDir();
        //or use it however you want
    }
}
1

It is not possible because the Java Language Specification says so (see 8.4.1. Formal Parameters):

The last formal parameter of a method or constructor is special: it may be a variable arity parameter, indicated by an ellipsis following the type.

Note that the ellipsis (...) is a token unto itself (§3.11). It is possible to put whitespace between it and the type, but this is discouraged as a matter of style.

If the last formal parameter is a variable arity parameter, the method is a variable arity method. Otherwise, it is a fixed arity method.

As to why only one and only the last parameter, that would be a guess, but probably because allowing that could lead to undecidable or ambiguous problems (eg consider what happens with method(String... strings, Object... objects)), and only allowing non-intersecting types would lead to complications (eg considering refactorings where previously non-intersecting types suddenly are), lack of clarity when it does or does not work, and complexity for the compiler to decide when it is applicable or not.

Mark Rotteveel
  • 100,966
  • 191
  • 140
  • 197
  • i wonder why someone woud answer on an > 4 year old question, which already has an accepted answer.. ^^ but thank you anyway – T_01 Jan 18 '18 at 16:58
  • @T_01 I was writing this as an answer to another question when it was closed as a duplicate of this question, so I decided to not let that go to waste and post it here instead. – Mark Rotteveel Jan 18 '18 at 19:42
0

I just read another question about this "pattern", but it is already removed, so I would like to propose a different approach to this problem, as I didn't see here this solution.

Instead to force the developer to wrapping the inputs parameter on List or Array, it will be useful to use a "curry" approach, or better the builder pattern.

Consider the following code:

/**
 * Just a trivial implementation
 */
public class JavaWithCurry {

    private List<Integer> numbers = new ArrayList<Integer>();
    private List<String> strings = new ArrayList<String>();

    public JavaWithCurry doSomething(int n) {

        numbers.add(n);

        return this;
    }

    public JavaWithCurry doSomething(String s) {

        strings.add(s);

        return this;

    }

    public void result() {

        int sum = -1;

        for (int n : numbers) {
            sum += n;
        }


        StringBuilder out = new StringBuilder();

        for (String s : strings) {

            out.append(s).append(" ");

        }

        System.out.println(out.toString() + sum);

    }

    public static void main(String[] args) {

        JavaWithCurry jwc = new JavaWithCurry();

        jwc.doSomething(1)
                .doSomething(2)
                .doSomething(3)
                .doSomething(4)
                .doSomething(5)
                .doSomething("a")
                .doSomething("b")
                .doSomething("c")
                .result();

    }

}

As you can see you in this way, you could add new elements of which type you need when you need.

All the implementation is wrapped.

Mario Santini
  • 2,905
  • 2
  • 20
  • 27
0

If you are not going to be passing a large number of Strings most of the time for the first argument you could provide a bunch of overloads that take different numbers of Strings and wrap them in an array before calling a method that takes the array as the first argument.

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) {
    // ...
    // ...
}
Alex - GlassEditor.com
  • 14,957
  • 5
  • 49
  • 49
0

follwing on Lemuel Adane (cant comment on the post, due to lack of rep :))

if you use

public void f(Object... args){}

then you may loop using How to determine an object's class (in Java)?

like for instance

{
   int i = 0;
   while(i< args.length && args[i] instanceof String){
         System.out.println((String) args[i]);
         i++ ;
   }
   int sum = 0;
   while(i< args.length){
         sum += (int) args[i];
         i++ ;
   }
   System.out.println(sum);
}

or anything you intend to do.

Community
  • 1
  • 1
user3617487
  • 155
  • 11
0

You can convert your varargs to arrays

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

then with some helper methods to convert your varargs to array like this:

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

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

Then you can use those helper methods to convert your vararged parameters.

doSomething(asArray("a", "b", "c", "d"), intsAsArray(1, 2, 3));
Mahpooya
  • 529
  • 1
  • 6
  • 18