T_01
T_01

Reputation: 1379

Java two varargs in one method

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!

Upvotes: 47

Views: 28298

Answers (13)

bifurcated
bifurcated

Reputation: 1

In addition to ZhongYu's answer, I want to add since you can have only one vararg parameter in a method, you can use the idea of monads and functional interfaces.
For example, I needed to assign a vertex of the first level to each vertex of the second level:

    interface CombineGraphVertex {
        SecondLevelVertex firstLevelVertex(String ... vertex);
    }

    interface SecondLevelVertex {
        Edge secondLevelVertex(String ... vertex);
    }

    interface Edge {
        void combine(Graph<String, DefaultEdge> graph);
    }
    
    CombineGraphVertex addEdge = vertex1 -> vertex2 -> graph -> {
        for (var v1 : vertex1) {
            for (var v2 : vertex2) {
                graph.addEdge(v1, v2);
            }
        }
    };
    
    addEdge.firstLevelVertex("A", "B", "C")
            .secondLevelVertex("1","2", "3")
            .combine(directedGraph);

Upvotes: 0

Mark Rotteveel
Mark Rotteveel

Reputation: 109264

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.

Upvotes: 1

Mahpooya
Mahpooya

Reputation: 559

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

Upvotes: 0

Bill K
Bill K

Reputation: 62789

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.

Upvotes: 4

user3617487
user3617487

Reputation: 175

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.

Upvotes: 0

Alex - GlassEditor.com
Alex - GlassEditor.com

Reputation: 15557

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) {
    // ...
    // ...
}

Upvotes: 0

Mario Santini
Mario Santini

Reputation: 3003

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.

Upvotes: 0

LEMUEL  ADANE
LEMUEL ADANE

Reputation: 8846

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

Upvotes: 2

user3709234
user3709234

Reputation: 31

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

Upvotes: 1

ZhongYu
ZhongYu

Reputation: 19712

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();

Upvotes: 11

rec
rec

Reputation: 10915

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

Upvotes: 45

William Morrison
William Morrison

Reputation: 11006

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.

Upvotes: 5

rgettman
rgettman

Reputation: 178343

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){

Upvotes: 19

Related Questions