为什么var-arg参数的类型“过度近似”?

时间:2011-06-30 13:20:53

标签: java types variadic-functions

如果我理解正确,Integer[]Object[]的子类型。你可以做一下

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

在玩var-args的过程中,我意识到,似乎编译器“过度使用”数组类型没有明显的原因。

例如,下面的程序会打印123 123。如果打印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
    }
}

我想我的问题可以归结为:如果JLS被定义为将T[]作为参数传递,那么是否会出现任何矛盾/问题,其中T是所有类型的最小上限给出的论点?


编辑:我意识到,在这种特殊情况下,我可能会使combine方法重载,以便Integer[]ideone demo)。尽管如此,仍然存在为什么选择这种设计的问题。

6 个答案:

答案 0 :(得分:6)

关于这个具体问题:

  

如果JLS被定义为传递T []作为参数,是否会出现任何矛盾/问题,其中T是所有参数类型的最小上界?

是的,因为数组不是只读的;它是可写的:

package com.example.test;

import java.util.Arrays;

public class Varargs3 {
    public static Object[] changeLastArgument(Object... objs) {
        if (objs.length > 0)
            objs[objs.length-1] = "Pow!";
        return objs;
    }

    public static void main(String[] args) {
        System.out.println(
            Arrays.toString(changeLastArgument(1,2,3))
        );
    }
}

打印

[1, 2, Pow!]

如果JLS是按照你要求的方式定义的(例如foo(T... args),如果你调用foo(a,b,c),那么编译器会构造一个类型为a,b,c的最小上界的数组,那么这种情况会允许运行时错误:调用changeLastArgument(1,2,3)会创建一个Integer[]类型的数组,但changeLastArgument()方法会尝试将"Pow!"分配给最后一个元素,你会得到一个运行时错误。

changeLastArgument()的声明是指定其输入类型,因此它应该能够假设其输入参数确实是Object[]而不是Object[]的子类型,因此它可以安全地修改输入参数。 (这类似于PECS principle - 为了使List<T>安全可读和可写,您不能使用任何通配符,如List<? extends T> - 这是安全的可读但是不安全可写 - 或List<? super T> - 安全可写但不安全可读。)

答案 1 :(得分:3)

要打印6作为结果,编译器必须足够聪明才能意识到,所有参数都可以装入类似的包装类中。

我想,对于一些非常罕见的情况,这只是太费力或太难以正确指定。


除了问题,好吧,看起来,简单的规则是:数组总是类型Object[](如果varargs类型是Object),这里是一些演示代码:

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

输出:

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

答案 2 :(得分:1)

我认为combine(1, 2, 3)会产生int[]而不是Integer[]。由于int[]数组不是Integer[]数组的实例,因此第一次检查失败,您将回退到concat块。

答案 3 :(得分:1)

JLS指定了这种行为(创建了一个变量arity参数类型的元素数组,即如果vararg方法是foo(Bar bar, Baz baz, T...),那么在方法调用上创建的数组的类型为{{1 }}),如果你找到了正确的位置:

来自JLS 8.4.1(Oracle网站目前遇到问题,我不得不使用互联网档案馆):

  

如果最后一个形式参数是a   它的类型为变量arity参数   被认为是定义正式的   类型为T []的参数。方法是   然后是一个可变的arity方法。   否则,它是一种固定的arity方法。   调用可变arity方法   可能包含更多实际参数   表达式比形式参数。   所有实际的参数表达式   那不符合正式的   变量前面的参数   arity参数将被评估和   结果存储到数组中   将传递给方法   调用(§15.12.4.2)。

来自JLS 15.12.4.2:

  

15.12.4.2评估参数评估参数的过程   列表不同,取决于是否   被调用的方法是一个固定的arity   方法或变量arity方法   (§8.4.1)。

     

如果被调用的方法是a   变量arity方法(§8.4.1)m,它   必须具有n> 0个形式参数。   m的最终形式参数   某些T必然有T []型,   并且必须调用m   k> = 0实际参数表达式。

     

如果使用k!= n actual调用m   参数表达式,或者,如果m是   用k = n实际参数调用   表达式和第k个类型   参数表达式不是赋值   兼容T [],然后参数   list(e1,...,en-1,en,... ek)是   被评估为好像被写为   (e1,...,en-1,new T [] {en,...,ek})。

     

参数表达式(可能   现在改写如上所述)   评估以产生参数值。   每个参数值对应于   正好是方法的正式之一   参数。

     

参数表达式(如果有的话)是   按顺序评估,从左到右   对。如果评价任何   参数表达式完成   突然,然后没有任何争论的一部分   右边的表达似乎   已经评估过,方法   调用突然完成   同样的原因。评估的结果   第j个参数表达式是第j个   参数值,对于1&lt; = j&lt; = n。评估   然后继续,使用参数   值,如下所述。

所以我保持原来的答案(见下文)。


我相信答案在声明中:

T[]

编译器匹配此方法,因此对于varargs,它会分配public static Object combine(Object... objs) 。没有理由分配Object[]


试验测试:

Integer[]

打印:

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

最后,如果希望编译器更具体,请使用泛型方法:

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

打印:

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

答案 4 :(得分:0)

嗯,你没有将Integer []发送到组合功能。这就是为什么它没有像你期望的那样工作。

使用

System.out.println(combine(new Integer[] {1, 2, 3}));

让它发挥作用。

答案 5 :(得分:0)

我最好的猜测是你没有指定varargs应该产生的类型。

以下显示了我的意思:

/**
 * @author The Elite Gentleman.
 *
 */
public class Test {

    public static Object combine(Object... objs) {
        System.out.println("combine()");
        System.out.println(objs.getClass().getName());

        if (objs instanceof Integer[]) {

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

        } else {

            String concat = "";
            for (Object o : objs) {
            System.out.println(o.getClass().getName());
            concat += o;
            }
            return concat;

        }
    }


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

        System.out.println("2");
        System.out.println(combine("1", "2", "3"));
        System.out.println(combine(1, 2, 3));
    }
}

输出:

1
combine()
[Ljava.lang.String;
java.lang.String
java.lang.String
java.lang.String
123
combine()
[Ljava.lang.Integer;
6
2
combine()
[Ljava.lang.Object;
java.lang.String
java.lang.String
java.lang.String
123
combine()
[Ljava.lang.Object;
java.lang.Integer
java.lang.Integer
java.lang.Integer
123

很明显,通过不传递“无类型”数组,JVM会将其转换为传递给Object[]方法的combine()

PS,我找不到JLS,因为Oracle服务器已关闭。