Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why is the type of the var-arg argument "over approximated"?

If I understand it correctly, Integer[] is a subtype of Object[]. You can for instance do

Object[] objs = new Integer[] { 1, 2, 3 };

While playing around with var-args I realized, that it seems like the compiler "over approixmates" the array type for no obvious reason.

The program below for instance, prints 123 123. Wouldn't it make sense / be more precise if it printed 123 6?

class Test {

    public static Object combine(Object... objs) {

        if (objs instanceof Integer[]) {

            int sum = 0;
            for (Integer i : (Integer[]) objs)
                sum += i;
            return sum;

        } else {

            String concat = "";
            for (Object o : objs)
                concat += o;
            return concat;

        }
    }

    public static void main(String[] args) {
        System.out.println(combine("1", "2", "3"));  // prints 123
        System.out.println(combine(1, 2, 3));        // prints 123
    }
}

I guess my question could be summed up as: Would any contradiction / problem arise if the JLS was defined to pass T[] as argument, where T was the least upper bound of the types of all arguments given?


Edit: I realize that I, in this particular case, could overload the the combine method to take Integer[] as well (ideone demo). Still, the question remains of why this design was chosen.

like image 934
aioobe Avatar asked Jun 30 '11 13:06

aioobe


3 Answers

The JLS specifies this behavior (creation of an array of elements of the type that is the variable arity parameter type, i.e. if the vararg method is foo(Bar bar, Baz baz, T...) then the array created on method invocation is of type T[]), if you find the right spot:

From JLS 8.4.1 (Oracle site having trouble at the moment, I had to use the Internet Archive):

If the last formal parameter is a variable arity parameter of type T, it is considered to define a formal parameter of type T[]. The method is then a variable arity method. Otherwise, it is a fixed arity method. Invocations of a variable arity method may contain more actual argument expressions than formal parameters. All the actual argument expressions that do not correspond to the formal parameters preceding the variable arity parameter will be evaluated and the results stored into an array that will be passed to the method invocation (§15.12.4.2).

From JLS 15.12.4.2:

15.12.4.2 Evaluate Arguments The process of evaluating of the argument list differs, depending on whether the method being invoked is a fixed arity method or a variable arity method (§8.4.1).

If the method being invoked is a variable arity method (§8.4.1) m, it necessarily has n>0 formal parameters. The final formal parameter of m necessarily has type T[] for some T, and m is necessarily being invoked with k >= 0 actual argument expressions.

If m is being invoked with k != n actual argument expressions, or, if m is being invoked with k=n actual argument expressions and the type of the kth argument expression is not assignment compatible with T[], then the argument list (e1, ... , en-1, en, ...ek) is evaluated as if it were written as (e1, ..., en-1, new T[]{en, ..., ek}).

The argument expressions (possibly rewritten as described above) are now evaluated to yield argument values. Each argument value corresponds to exactly one of the method's n formal parameters.

The argument expressions, if any, are evaluated in order, from left to right. If the evaluation of any argument expression completes abruptly, then no part of any argument expression to its right appears to have been evaluated, and the method invocation completes abruptly for the same reason.The result of evaluating the jth argument expression is the jth argument value, for 1 <= j <= n. Evaluation then continues, using the argument values, as described below.

So I maintain my original answer (see below).


I believe the answer is in the declaration:

public static Object combine(Object... objs)

The compiler matches this method, and therefore for varargs it allocates an Object[]. There is no reason for it to allocate an Integer[].


trial test:

package com.example.test;
public class Varargs1 {
    public static void varargs(Object... objs) {
        System.out.println(objs.getClass());
    }

    public static void main(String[] args) {
        varargs("1", "2", "3");
        varargs(1, 2, 3);

        Integer[] ints = {1,2,3};
        varargs(ints); // Eclipse yields the following warning:
        /* 
         * The argument of type Integer[] should explicitly be 
         * cast to Object[] for the invocation of the varargs 
         * method varargs(Object...) from type Varargs1. 
         * It could alternatively be cast to Object for a 
         * varargs invocation
         */
    }
}

which prints:

class [Ljava.lang.Object;
class [Ljava.lang.Object;
class [Ljava.lang.Integer;

Finally, if you want the compiler to be more specific, use generic methods:

package com.example.test;

public class Varargs2 {
    public static <T> void varargs(T... objs) {
        System.out.println(objs.getClass());
    }

    public static void main(String[] args) {
        varargs("1", "2", "3");
        varargs(1, 2, 3);
        varargs(1, "2", 3); // warning from Eclipse:
        /*
         * Type safety : A generic array of 
         * Object&Comparable<?>&Serializable
         * is created for a varargs parameter
         */
    }
}

which prints:

class [Ljava.lang.String;
class [Ljava.lang.Integer;
class [Ljava.lang.Comparable;
like image 193
Jason S Avatar answered Nov 20 '22 01:11

Jason S


To print 6 as an result, the compiler would have to be clever enough to realize, that all arguments can be boxed into a similar wrapper class.

I guess, this is just too much effort or too difficult to specify correctly for some very rare cases.


Besides the question, well, it looks like, the simple rule is: the array is always of type Object[] (if the varargs type is Object), here's some demonstration code:

public static void main (String[] args) {
    temp("1", "2", "3");
    temp(1,2,3);
    temp(String.class, Integer.class);
}

public static void temp(Object... objs) {
    System.out.println(objs.getClass());
}

Output:

class [Ljava.lang.Object;
class [Ljava.lang.Object;
class [Ljava.lang.Object;
like image 23
Andreas Dolk Avatar answered Nov 20 '22 02:11

Andreas Dolk


It looks to me like combine(1, 2, 3) will yield a int[] rather than Integer[]. Since an int[] array is not an instance of an Integer[] array, the first check fails, and you fall back to the concat block.

like image 1
Ray Avatar answered Nov 20 '22 02:11

Ray