Varargs and the '...' Argument

Java: Function with one varargs argument and function with same name and one argument of same type is allowed?

Method overloading resolution has 3 stages. Only on the 3rd and last stage it considers methods with varags (such as your public void buy(Object... o)), so if a matching method is found in one of the first 2 stages, the varargs methods are ignored, and the non-varag matching method is chosen.

Therefore both calls result in public void buy(Object o) being chosen.

Will it always select the non-varargs function when I pass only one argument?

It will always select the non-varargs method when you pass only one argument, unless the compile time type of that argument is an array:

Object[] arr = new Object[]{"a string"};
consumer.buy(arr);

Passing null will also cause the compiler to select the varargs method:

consumer.buy(null);

Here's the relevant JLS 15.12.2. Compile-Time Step 2: Determine Method Signature quote:

The process of determining applicability begins by determining the potentially applicable methods (§15.12.2.1). Then, to ensure compatibility with the Java programming language prior to Java SE 5.0, the process continues in three phases:

  1. The first phase performs overload resolution without permitting boxing or unboxing conversion, or the use of variable arity method invocation. If no applicable method is found during this phase then processing continues to the second phase.

    This guarantees that any calls that were valid in the Java programming language before Java SE 5.0 are not considered ambiguous as the result of the introduction of variable arity methods, implicit boxing and/or unboxing. However, the declaration of a variable arity method (§8.4.1) can change the method chosen for a given method method invocation expression, because a variable arity method is treated as a fixed arity method in the first phase. For example, declaring m(Object...) in a class which already declares m(Object) causes m(Object) to no longer be chosen for some invocation expressions (such as m(null)), as m(Object[]) is more specific.

  2. The second phase performs overload resolution while allowing boxing and unboxing, but still precludes the use of variable arity method invocation. If no applicable method is found during this phase then processing continues to the third phase.

    This ensures that a method is never chosen through variable arity method invocation if it is applicable through fixed arity method invocation.

  3. The third phase allows overloading to be combined with variable arity methods, boxing, and unboxing.

Method overloading with variable arguments (varargs)

This behaviour is due to the fact that int is more specific than double while there is no such comparison between int and boolean.

As specified in the JLS section 15.12.2.5 (emphasis mine):

One applicable method m1 is more specific than another applicable method m2, for an invocation with argument expressions e1, ..., ek, if any of the following are true:

  • ...
  • m2 is not generic, and m1 and m2 are applicable by variable arity invocation, and where the first k variable arity parameter types of m1 are S1, ..., Sk and the first k variable arity parameter types of m2 are T1, ..., Tk, the type Si is more specific than Ti for argument ei for all i (1 ≤ i ≤ k). Additionally, if m2 has k+1 parameters, then the k+1'th variable arity parameter type of m1 is a subtype of the k+1'th variable arity parameter type of m2.

What more specific actually means is later defined with subtyping:

A type S is more specific than a type T for any expression if S <: T.

This means that S is more specific than T is S is a subtype of T. For primitive types, this comes down to the following properties:

  • double > float
  • float > long
  • long > int
  • int > char
  • int > short
  • short > byte

Notice that boolean is not there.

As such,

public static void main(String[] args) {
movie();
}

static void movie(int... x) { }
static void movie(short... x) { }
static void movie(double... x) { }
static void movie(byte... x) { }

compiles and movie(byte... x) will be called because it is the most specific.

However,

public static void main(String[] args) {
movie();
}

static void movie(int... x) { }
static void movie(boolean... x) { }

does not compile because boolean cannot be compared to int.

varargs as input parameter to a function in java 8

You cannot use the varargs syntax in this case as it's not a method parameter.

Depending on what you're using the Function type for, you may not even need it at all and you can just work with your methods as they are without having to reference them through functional interfaces.

As an alternative you can define your own functional interface like this:

@FunctionalInterface
public interface MyFunctionalInterface<T, R> {
R apply(T... args);
}

then your declaration becomes:

MyFunctionalInterface<String, String> doWork = a -> doSomethingWithArray(a);

and calling doWork can now be:

String one = doWork.apply("one");
String two = doWork.apply("one","two");
String three = doWork.apply("one","two","three");
...
...

note - the functional interface name is just a placeholder and can be improved to be consistent with the Java naming convention for functional interfaces e.g. VarArgFunction or something of that ilk.

varargs and the '...' argument

From the docs on varargs:

The three periods after the final
parameter's type indicate that the
final argument may be passed as an
array or as a sequence of arguments.

So you can pass multiple arguments or an array.

The following works just fine:

class VarargTest {
public static void main(String[] args) {
Object[] params = {"x", 1.2345f};
String s = String.format("%s is %.2f", params);
System.out.println(s); // Output is: x is 1.23
}
}

Consumer with varargs

Variable arity parameter (varargs) is a syntactic element, a special token indicating that a method (or a constructor) expects 0 or more arguments of that type. And these arguments will be wrapped by an array (so that technically they will constitute a single argument) when the method gets executed.

Its usage is limited: a method can have only one parameter of variable arity and it must be defined in the last position.

Variable arity parameter isn't a type by itself. If you'll try to use eclipses ... anywhere apart from the last position of a method (constructor) declaration, you'll get a compile error (take a look at JLS for more information).

In order to use a variable number of arguments in a function, you can define a functional interface with a method of variable arity. And this method is the only place where elipses ... needs to be used.

@FunctionalInterface
public interface MyConsumer<T> {
void accept(T... items);
}
MyConsumer<String> myConsumer = items -> Stream.of(items).forEach(System.out::println);

myConsumer.accept("A", "B", "C");

Will give an output:

A
B
C

Varargs and argument less method

First of call, the parameter-less overloading gets called because its signature is more specific than that of the overlauding with varargs. It is in general a very bad idea to have two overloaded methods which perform a completely different operation. So let's assume that the parameter-less method does the same thing as the varargs method when called without arguments, that is, the parameter-less method is a specialization of the varargs method.

Then a use-case is the following. Calling a varargs method always requires creating an array. Although, certainly at first, I wouldn't think about such minor optimizations too much, but it is an overhead which might, in some cases (for example in tight loop), be considerable enough. The parameter-less version of the method does not require creating an array, and additionally also may contain other optimizations for the specific case.

Sometimes, one sees more than one specializations, one with no arguments, one with one, one with two, and a general method. For example:

void doSomething() { ... }
void doSomething(String a1) { ... }
void doSomething(String a1, String a2) { ... }
void doSomething(String... as) { ... }

But I suggest to only do this in a late stage of development, if at all.

How to add arguments to varargs?

You can write a method like this:

public static Object[] merge(Object o, Object... arr) {
Object[] newArray = new Object[arr.length + 1];
newArray[0] = o;
System.arraycopy(arr, 0, newArray, 1, arr.length);

return newArray;
}

and, subsequently:

m2(merge("added", objs));

Java varargs method with single null argument

It's not about null specifically, it's about the null expression's type.

A null expression doesn't have to be typed, but it can be. Here is a trivial example:

String x = null;
System.out.println(x);

This is very slightly different compared to:

System.out.println(null);

Note that println has a boatload of overloads - completely different methods (and javac, the compiler, must pick which exact overload to invoke at compile time), that are all named println, all of which have 1 parameter. The first snippet is easy for the compiler: You clearly mean to call that println that takes a single String as parameter. However, the second is ambiguous. If I attempt to compile the second snippet, the compiler will literally tell me so:

error: reference to println is ambiguous

This shows how, even though the runtime behaviour of this would seem to be 100% identical (both null and x are expressions whose value will definitely be null), turns out, at least at compile time, this is not true.

The same principle can explain what you see here, with your varargs, in a slightly different way. Given:

void validateLocationIds(String... locationIds) {

That's just syntax sugar. Specifically, it's syntax sugar for:

  • The method's signature is validateLocationIds(String[] locationIds).
  • However, you are telling the compiler, during the compilation of calls to this method, to treat a sequence of arguments that can all be treated as String arguments to go ahead and treat that as if the caller had written validateLocationIds(new String[] {a, b, c}) instead of what you typed (validateLocationIds(a, b, c);).

Crucially the compiler will only do that if it has to - if the code would not compile if it didn't do this 'fold the args into an array'.

This then gets us (presumably, there are some more exotic alternate explanations, this is merely the most likely) to an explanation. Given:

String x = null;
validateLocationIds(x);

There is only one way for the compiler to make this work: By treating x as a String and therefore syntax sugaring that into new String[] {x}, i.e. a new string array of size 1 whose first and only entry is null. Because otherwise it would not compile; a variable of type String simply isn't a String[]. Even if the variable's value is currently null which is also a valid value for String[] - because how could the compiler possibly know that for sure? It can't, so it won't just go: Oohhh,, I see, a variable of type String, but, I know its null so I can treat it as whatever the heck I want, really. It does not do that.

In contrast to spelling it out literally:

validateLocationIds(null);

This is not ambiguous, unlike our println example: The Java Language Spec, in the section on picking between multiple different overloads, explicitly states that the compiler must error out with a 'call is ambiguous' error, but, when it's about whether to apply varargs sugaring or not, that is not the case: The array "wins", so to speak. In other words, if the call could both be treated as a straight method call (the expression is the array), as well as sugaring into an array (you could zip that one arg into a 1-len array and pass that instead), then 'just treat as array' wins.

This makes sense. Imagine that would be an error condition. Then you could NEVER pass an object array to void foo(Object...). After all, an array -is also an object-, and could therefore be 'zipped'.

When do you use varargs in Java?

Varargs are useful for any method that needs to deal with an indeterminate number of objects. One good example is String.format. The format string can accept any number of parameters, so you need a mechanism to pass in any number of objects.

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

C++ Multiple function parameters with varargs of a specific type

You need to define two templates types, one for the input and one for the parameter pack.

The function would then look like this:

template <typename T, typename ... Ts>
bool func(const T & input, const Ts & ... args);

To guarantee all the types are the same, we can combine std::conjunction with std::is_same.

We can also make use of static_assert so that we can generate an error at compile time if the condition is not satisfied.

It allows us to provide a custom error message so that the error would be clearer and easier for the user to understand.

It would then become:

template <typename T, typename ... Ts>
bool func(const T & input, const Ts & ... args)
{
static_assert(std::conjunction_v<std::is_same<T, Ts>...>, "Error: All parameters must be of the same type.");

//...

return true;
}

You could also use std::enable_if instead of static_assert.

I personally prefer to use static_assert because we can make the error more explicit. If we use std::enable_if, in case of failure, the user will still need to check on his/her own what was the evaluated condition to find out what was wrong.

In that case, the function would be like:

template <typename T, typename ... Ts>
std::enable_if_t<std::conjunction_v<std::is_same<T, Ts>...>, bool>
func(const T & input, const Ts & ... args)
{
//...

return true;
}


Related Topics



Leave a reply



Submit