Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How can one explain this seemingly inconsistent Java varargs behavior?

If I write the Java method

public static void f(int... x) {
    for (int a: x) {
        System.out.println(a);
    }
}

then I can call this method via

f(1, 2, 3);

as well as

f(new int[]{1, 2, 3});

and both calls are treated exactly the same. However, the two calls

Arrays.asList(1, 2, 3)              // (a) produces a three-element Integer list

and

Arrays.asList(new int[]{1, 2, 3})   // (b) produces a one-element list of Integer arrays

are not treated the same. The section on evaluating arguments in the JLS states "The final formal parameter of m necessarily has type T[] for some T" so why isn't the "final formal parameter" in case (b) above not an integer array which would create a three-element list, like case (a)?

The only thing I can think of is that Arrays.asList is generic, and something about type compatibility (mentioned in JLS Section 15.12.4.2) is at play here, but I can't quite put my finger on the actual sentence in the JLS that makes the two cases different.

The commentary in the JLS talks about the need to craft these semantics carefully in order to handle the interplay between "parameterized types and array types that occurs in a Java Virtual Machine with erased generics" but what we end up is my function f and Arrays.asList appearing to behave inconsistent. In the former case I can "unpack" or "pack" my args and all is the same; in the latter case, the two cases are different.

How is this explained in language-lawyer terms?

like image 605
Ray Toal Avatar asked Nov 26 '14 06:11

Ray Toal


People also ask

What is Varargs in Java & How it works?

Varargs is a short name for variable arguments. In Java, an argument of a method can accept arbitrary number of values. This argument that can accept variable number of values is called varargs. The syntax for implementing varargs is as follows: accessModifier methodName(datatype… arg) { // method body }

Is it good to 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.

How many values can be accommodated by Varargs in Java?

There is no limit. There can be any number of methods or constructors with one Vararg per each method or constructor.


2 Answers

Arrays.asList is defined as having parameter T..., and the body will see it as one argument of type T[]. However, T must be a reference type (like any type parameter), and int is not a reference type. Because of that, there's no way to make new int[]{...} compatible with an array for varargs purposes. (And the result would be List<int> which isn't allowed.) The only alternative is to treat it as a single object, and thus T here will be int[], which is a reference type, and the function will return a List<int[]>.

I think that's the reason, although I would still need to look up the exact language. It probably involves the type inference rules, which I haven't mastered.

However, I did try this:

List x = Arrays.asList(new int[]{1, 2, 3});
List y = Arrays.asList(new Integer[]{1, 2, 3});
System.out.println(x.size());   
System.out.println(y.size());   

(note that I deliberately used a List raw type, something I wouldn't do in real code). The output:

1
3
like image 165
ajb Avatar answered Oct 06 '22 11:10

ajb


    Arrays.asList(1, 2, 3);

1 2 3 are autoboxed to Integer objects which are then placed in an Object[]

    Arrays.asList(new int[]{1, 2, 3});

forces the 3 integers into a primitive int array.

Arrays.asList() expects Object[], so it places your int[] array into a single element Object[]

like image 42
slipperyseal Avatar answered Oct 06 '22 09:10

slipperyseal