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

发布于 2024-11-18 09:39:52 字数 1197 浏览 4 评论 0原文

如果我理解正确的话,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 演示)。尽管如此,问题仍然是为什么选择这种设计。

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.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(6

骄傲 2024-11-25 09:39:52

关于这个具体问题:

如果 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!]

如果您调用 foo(a,b,c), If the JLS was Defined as you are questions (eg for foo(T... args)) code> 然后编译器构造一个类型 a,b,c) 的最小上界的数组,那么这种情况将允许运行时错误:调用 changeLastArgument(1,2,3)会创建一个类型的数组Integer[],但 changeLastArgument() 方法会尝试将 "Pow!" 分配给最后一个元素,并且您将获得运行时错误。

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

As to this specific question:

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?

Yes, because the array is not read-only; it's writable:

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

which prints

[1, 2, Pow!]

If the JLS was defined the way you are asking (e.g. for foo(T... args), if you call foo(a,b,c) then the compiler constructs an array of the least upper bound of the types a,b,c), then this case would allow a runtime error: the invocation of changeLastArgument(1,2,3) would create an array of type Integer[], but the changeLastArgument() method would attempt to assign "Pow!" to the last element and you'd get a runtime error.

The declaration of changeLastArgument() is specifying its input types, and therefore it should be able to assume its input argument is truly an Object[] and not a subtype of Object[], so that it can safely modify the input arguments. (This is similar to the PECS principle -- in order for a List<T> to be both safely readable and writable, you can't use any wildcards like List<? extends T> -- which is safely readable but not safely writable -- or List<? super T> -- which is safely writable but not safely readable.)

缱绻入梦 2024-11-25 09:39:52

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

我想,对于一些非常罕见的情况来说,正确指定需要太多的努力或太困难。


除了问题之外,看起来简单的规则是:数组总是类型为Object[](如果可变参数类型为Object) 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());
}

输出:

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

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;
旧人哭 2024-11-25 09:39:52

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

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.

独守阴晴ぅ圆缺 2024-11-25 09:39:52

JLS 指定了此行为(创建变量参数类型的元素数组,即如果 vararg 方法为 foo(Bar bar, Baz baz, T...) 那么如果您找到正确的位置,则在方法调用时创建的数组的类型为 T[]):

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

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

从 JLS 15.12.4.2 开始:

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

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

如果调用 m 时 k != n 实际
参数表达式,或者,如果 m 是
使用 k=n 实际参数调用
表达式和第 k 个的类型
参数表达式不是赋值
与 T[] 兼容,则参数
列表 (e1, ... , en-1, en, ...ek) 是
评估就像写成
(e1, ..., en-1, new T[]{en, ..., ek})。

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

参数表达式(如果有)是
按从左到右的顺序评估
正确的。如果评价任何一个
参数表达式完成
突然,然后没有任何争论的一部分
其右侧的表达式似乎是
已经进行了评估,并且该方法
调用突然完成
同样的原因。评估结果
第 j 个参数表达式是第 j 个
参数值,1 <= j <= n。评估
然后继续,使用参数
值,如下所述。

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


我相信答案就在声明中:

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

打印:

class [Ljava.lang.String;
class [Ljava.lang.Integer;
class [Ljava.lang.Comparable;

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;
一身软味 2024-11-25 09:39:52

好吧,您没有将 Integer[] 发送到组合函数中。这就是为什么它没有按您的预期工作。

使用

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

使其发挥作用。

Well you are not sending a Integer[] into combine function. That's why it's not working as you expect.

Use

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

to make it work.

烟酒忠诚 2024-11-25 09:39:52

我最好的猜测是您没有指定可变参数应产生的类型。

下面显示了我的意思:

/**
 * @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 将其转换为传递给 combine()Object[] 的对象。代码>方法。

PS,由于 Oracle 服务器已关闭,我找不到 JLS。

My best guess is that you haven't specified a type that the varargs should incur.

The following shows what I mean:

/**
 * @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));
    }
}

Output:

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

It's clear that by not passing an "untyped" array, the JVM converts it to an Object[] that is passed to the combine() method.

PS, I couldn't find the JLS as the Oracle Server is down.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文