请考虑以下代码:
public class Converter {
public <K> MyContainer<K> pack(K key, String[] values) {
return new MyContainer<>(key);
}
public MyContainer<IntWrapper> pack(int key, String[] values) {
return new MyContainer<>(new IntWrapper(key));
}
public static final class MyContainer<T> {
public MyContainer(T object) { }
}
public static final class IntWrapper {
public IntWrapper(int i) { }
}
public static void main(String[] args) {
Converter converter = new Converter();
MyContainer<IntWrapper> test = converter.pack(1, new String[]{"Test", "Test2"});
}
}
上面的代码编译没有问题。但是,如果在String[]
个签名和String...
到pack
中将new String[]{"Test", "Test2"}
更改为"Test", "Test2"
,则编译器会抱怨对converter.pack
的调用不明确的。
现在,我可以理解为什么它可以被认为是模棱两可的(因为int
可以自动装入Integer
,从而匹配K
)的条件或缺乏条件。但是,我无法理解的是,如果您使用的是String[]
而不是String...
,那么为什么不存在歧义。
有人可以解释这种奇怪的行为吗?
答案 0 :(得分:14)
你的1 st 案件非常简单。以下方法:
public MyContainer<IntWrapper> pack(int key, Object[] values)
与参数完全匹配 - (1, String[])
。来自JLS Section 15.12.2:
第一阶段(§15.12.2.2)执行重载解析而不允许装箱或拆箱转换
现在,将这些参数传递给第二个方法时没有涉及装箱。由于Object[]
是String[]
的超级类型。并且,即使在Java 5之前,为String[]
参数传递Object[]
参数也是一个有效的调用。
在你的第二种情况下,既然你已经使用了var-args,那么方法重载分辨率将使用var-args,装箱或拆箱来完成,按照JLS部分中解释的第3阶段:
第三阶段(§15.12.2.4)允许重载与变量arity方法,装箱和拆箱相结合。
注意,由于使用了 var-args ,第二阶段不适用于此:
第二阶段(§15.12.2.3)在允许装箱和拆箱的同时执行重载解析,但仍然排除使用变量arity方法调用。
现在发生的事情是编译器没有正确地推断出类型参数* (实际上,它正确推断它,因为type参数被用作形式参数,见这个答案结束时的更新)。所以,对于你的方法调用:
MyContainer<IntWrapper> test = converter.pack(1, "Test", "Test2");
编译器应该从LHS推断泛型方法中K
的类型为IntWrapper
。但似乎它推断K
为Integer
类型,因此您的方法现在同样适用于此方法调用,因为它们都需要var-args
或{{ 1}}。
但是,如果该方法的结果没有分配给某个引用,那么我可以理解编译器无法推断出正确的类型,因为在这种情况下,给出歧义错误是完全可以接受的:
boxing
可能我想,为了保持一致性,第一种情况也显得模棱两可。但是,我并不确定,因为我没有从JLS或其他官方参考资料中找到任何有关此问题的可信来源。我将继续搜索,如果我找到一个,将更新答案。
如果更改方法调用以提供显式类型信息:
converter.pack(1, "Test", "Test2");
现在,类型MyContainer<IntWrapper> test = converter.<IntWrapper>pack(1, "Test", "Test2");
将被推断为K
,但由于IntWrapper
无法转换为1
,因此该方法将被废弃,第二种方法将被调用它会完美地运作。
IntWrapper
但是,在这种情况下并没有这样做。所以这可能是一个错误。
*或者,当类型未作为参数传递时,我可能无法完全理解编译器如何推断类型参数。因此,为了更多地了解这一点,我尝试了 - JLS §15.12.2.7和JLS §15.12.2.8,这是关于编译器如何推断类型参数的,但这完全超出了我的头脑。
所以,现在你必须忍受它,并使用替代方法(提供显式类型参数)。
正如@ zhong.j.yu。在评论中最后解释的那样,编译器仅在第15.12.2.8节中对类型推断应用,当它无法根据15.12.2.7部分推断它时。但是在这里,它可以从传递的参数中将类型推断为public static <T> HashSet<T> create(int size) {
return new HashSet<T>(size);
}
// Type inferred as `Integer`, from LHS.
HashSet<Integer> hi = create(10);
,因为类型参数显然是方法中的格式参数。
所以,是的编译器正确地将类型推断为Integer
,因此歧义是有效的。现在我觉得这个答案已经完成了。
答案 1 :(得分:3)
在这里,以下两种方法的区别: 方法1:
public MyContainer<IntWrapper> pack(int key, Object[] values) {
return new MyContainer<>(new IntWrapper(""));
}
方法2:
public MyContainer<IntWrapper> pack(int key, Object ... values) {
return new MyContainer<>(new IntWrapper(""));
}
方法2与
一样好public MyContainer<IntWrapper> pack(Object ... values) {
return new MyContainer<>(new IntWrapper(""));
}
这就是为什么你会有歧义......
修改强> 是的我想说它们在编译时是一样的。使用变量参数的全部目的是使用户能够在他/她不确定时定义方法 给定类型的参数数量。
因此,如果你使用一个对象作为变量参数,你只需要说编译器,我不确定我将发送多少个对象,另一方面,你说,&#34;我传递一个整数,未知数量的对象&#34;。对于编译器,整数也是一个对象。
如果要检查有效性,请尝试传递一个整数作为第一个参数,然后传递String的变量参数。你会看到差异。
例如:
public class Converter {
public static void a(int x, String... y) {
}
public static void a(String... y) {
}
public static void main(String[] args) {
a(1, "2", "3");
}
}
另外,请不要互换地使用数组和变量args,它们有一些不同的用途。
当您使用varargs时,该方法不会期望一个数组,但是相同类型的不同参数可以以索引方式访问。
答案 2 :(得分:3)
在这种情况下
(1) m(K, String[])
(2) m(int, String[])
m(1, new String[]{..});
m(1)满足15.12.2.3. Phase 2: Identify Matching Arity Methods Applicable by Method Invocation Conversion
m(2)满足15.12.2.2. Phase 1: Identify Matching Arity Methods Applicable by Subtyping
编译器在第1阶段停止;它发现m(2)是该阶段唯一适用的方法,因此选择了m(2)。
在var arg case中
(3) m(K, String...)
(4) m(int, String...)
m(1, str1, str2);
m(3)和m(4)都满足15.12.2.4. Phase 3: Identify Applicable Variable Arity Methods。两者都没有比另一个更具体,因此含糊不清。
我们可以将适用的方法分为4组:
规范合并了第3组和第4组,并在第3阶段对它们进行处理。因此不一致。
为什么他们这样做? Maye他们只是厌倦了它。
另一个批评是,不应该有所有这些阶段,因为程序员不会这样思考。我们应该不加选择地找到所有适用的方法,然后选择最具体的方法(有一些机制来避免装箱/拆箱)
答案 3 :(得分:0)
首先,这只是一些第一线索......可以编辑更多。
编译器始终搜索并选择最具体的方法。尽管阅读有点笨拙,但它在JLS 15.12.2.5中都有所指定。因此,通过调用
converter.pack(1,&#34; Test&#34;,&#34; Test2&#34;)
编译器无法确定1
是否应解散为K
或int
。换句话说,K可以应用于任何类型,因此它与int / Integer处于同一级别。
不同之处在于参数的数量和类型。考虑new String[]{"Test", "Test2"}
是一个数组,而"Test", "Test2"
是String类型的两个参数!
converter.pack(1); //模棱两可,编译错误
converter.pack(1,null); //调用方法2,编译器警告
converter.pack(1,new String [] {}); //调用方法2,编译器警告
converter.pack(1,new Object()); //不明确,编译错误
converter.pack(1,new Object [] {}); //调用方法2,无警告