Sma*_*ker 34 java generics overloading variadic-functions
请考虑以下代码:
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"});
}
}
Run Code Online (Sandbox Code Playgroud)
上面的代码编译没有问题.但是,如果一个人改变String[],以String...在两个pack签名和new String[]{"Test", "Test2"}到"Test", "Test2"时,编译器抱怨调用converter.pack是不明确的.
现在,我可以理解为什么它可以被认为是模棱两可的(因为它int可以被自动装入一个Integer,从而匹配条件,或者缺乏条件K).不过,我能不明白是为什么模糊是不存在的,如果你使用String[]代替String....
有人可以解释这个奇怪的行为吗?
Roh*_*ain 14
你的第一个案例非常简单.以下方法:
public MyContainer<IntWrapper> pack(int key, Object[] values)
Run Code Online (Sandbox Code Playgroud)
是一个完全匹配的参数 - (1, String[]).来自JLS第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方法调用.
现在发生的事情是编译器没有正确地推断出类型参数*(实际上,它正确地推断它,因为类型参数被用作形式参数,请参阅本答案末尾的更新).所以,对于你的方法调用:
MyContainer<IntWrapper> test = converter.pack(1, "Test", "Test2");
Run Code Online (Sandbox Code Playgroud)
编译器应该从LHS 推断出K泛型方法的类型IntWrapper.但似乎它推断K是一种Integer类型,因为你的方法现在同样适用于这种方法调用,因为它们都需要var-args或者boxing.
但是,如果该方法的结果未分配给某个引用,那么我可以理解编译器无法推断出正确的类型,因为在这种情况下,给出歧义错误是完全可以接受的:
converter.pack(1, "Test", "Test2");
Run Code Online (Sandbox Code Playgroud)
可能是我猜,只是为了保持一致性,第一种情况也明显不明确.但是,我并不确定,因为我没有从JLS或其他官方参考资料中找到任何有关此问题的可信来源.我将继续搜索,如果我找到一个,将更新答案.
如果更改方法调用以提供显式类型信息:
MyContainer<IntWrapper> test = converter.<IntWrapper>pack(1, "Test", "Test2");
Run Code Online (Sandbox Code Playgroud)
现在,类型K将被推断为IntWrapper,但由于1不可转换为IntWrapper,该方法被丢弃,第二个方法将被调用,它将完美地工作.
坦率地说,我真的不知道这里发生了什么.我希望编译器在第一种情况下从方法调用上下文中推断出类型参数,因为它适用于以下问题:
public static <T> HashSet<T> create(int size) {
return new HashSet<T>(size);
}
// Type inferred as `Integer`, from LHS.
HashSet<Integer> hi = create(10);
Run Code Online (Sandbox Code Playgroud)
但是,在这种情况下它并没有这样做.所以这可能是一个错误.
*或者,当类型未作为参数传递时,我可能不完全理解编译器如何推断类型参数.因此,为了更多地了解这一点,我试图通过--JLS§15.12.2.7和JLS§15.12.2.8,这是关于编译器如何推断类型参数,但这完全超出了我的头脑.
所以,现在你必须忍受它,并使用替代方法(提供显式类型参数).
正如@ zhong.j.yu.在评论中最后解释的那样,编译器仅在第15.12.2.8节中对类型推断应用,当它无法根据15.12.2.7部分推断它时.但是在这里,它可以Integer从传递的参数推断出类型,因为类型参数显然是方法中的格式参数.
所以,是的编译器正确地将类型推断为Integer,因此歧义是有效的.现在我觉得这个答案已经完成了.