如果我理解正确,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)。尽管如此,仍然存在为什么选择这种设计的问题。
答案 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服务器已关闭。