Java:通用方法重载模糊
考虑下面的代码:
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
的条件相匹配或缺less)。 但是,我不明白的是,如果你使用String[]
而不是String...
,那么歧义就不存在了。
有人可以解释这种奇怪的行为?
你的第一个案子非常简单。 以下方法:
public MyContainer<IntWrapper> pack(int key, Object[] values)
与参数完全匹配 – (1, String[])
。 从JLS第15.12.2节 :
第一阶段(§15.12.2.2)执行重载parsing而不允许装箱或拆箱转换
现在,在将这些parameter passing给第二种方法时没有涉及到拳击。 由于Object[]
是String[]
的超级types。 在Java 5之前,传递Object[]
参数的String[]
参数是一个有效的调用。
编译器似乎玩你的第二个案例:
在第二种情况下,由于您已经使用了var-args,所以重载parsing的方法将使用var-args和boxing或unboxing完成,按照JLS部分中的第三个阶段:
第三阶段(§15.12.2.4)允许将重载与variables方法,装箱和拆箱相结合。
请注意,由于使用了var-args ,第二阶段在这里不适用:
第二阶段(§15.12.2.3)在允许装箱和拆箱的同时执行重载parsing,但是仍然排除了使用variables方法调用。
现在在这里发生了什么是编译器不正确推断types参数* (实际上,它正确推断它为types参数被用作forms参数,看到更新结束这个答案)。 所以,对于你的方法调用:
MyContainer<IntWrapper> test = converter.pack(1, "Test", "Test2");
编译器应该从LHS中推断出generics方法中的K
types是IntWrapper
。 但是似乎推断K
是一个Integer
types,由于这两个方法现在同样适用于这个方法调用,因为它们都需要var-args
或boxing
。
但是,如果这个方法的结果没有被赋值给某个引用,那么我可以理解,编译器不能在这种情况下推断出正确的types,哪里是完全可以接受的歧义错误:
converter.pack(1, "Test", "Test2");
可能是我想,只是为了保持一致性,这也是第一种情况的含糊不清。 但是,我也不太确定,因为我没有findJLS的可信来源,或者其他官方提到的这个问题。 我会继续search,如果我find一个,将更新答案。
让我们通过显式的types信息欺骗编译器:
如果您更改方法调用以提供显式types信息:
MyContainer<IntWrapper> test = converter.<IntWrapper>pack(1, "Test", "Test2");
现在,typesK
将被推断为IntWrapper
,但由于1
不能转换为IntWrapper
,所以该方法被丢弃,并且第二个方法将被调用并且它将工作得很好。
坦率地说,我真的不知道这里发生了什么。 我期望编译器在第一种情况下也可以从方法调用上下文中推断出types参数,因为它适用于以下问题:
public static <T> HashSet<T> create(int size) { return new HashSet<T>(size); } // Type inferred as `Integer`, from LHS. HashSet<Integer> hi = create(10);
但是,在这种情况下是不行的。 所以这可能是一个错误。
*或者可能是我不明白编译器如何推断types参数,当types不作为parameter passing。 所以,为了更多地了解这个,我尝试了一下–JLS第15.12.2.7节和JLS第15.12.2.8节 ,它是关于编译器如何推断types参数的,但是这完全在我头顶。
所以,现在你必须忍受它,并使用替代(提供显式types的参数)。
事实certificate,编译器并没有玩什么窍门:
正如@ zhong.j.yu所说的最后解释,编译器只对types推断使用15.12.2.8节,当它不能按照15.12.2.7节推断时。 但是在这里,它可以从传入的参数中推断出types为Integer
,因为types参数显然是方法中的格式参数。
所以,是的编译器正确地推断types为Integer
,因此歧义是有效的。 现在我认为这个答案是完整的。
在这里你去,下面两种方法之间的区别:方法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("")); }
这就是为什么你得到一个模糊不清
编辑是的我想说,他们是相同的编译。 使用可变参数的目的是让用户在不确定给定types的参数个数的时候定义一个方法。
所以如果你使用一个对象作为可变参数,你只需要说编译器,我不知道我将发送多less个对象,另一方面,你说“我传递一个整数和未知数目的对象”。 对于编译器来说,整数也是一个对象。
如果你想检查有效性尝试传递一个整数作为第一个参数,然后传递一个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"); } }
另外,请不要使用数组和variables参数,它们有一些不同的用途。
当你使用可变参数时,这个方法并不期望一个数组,但是可以以索引的方式访问同一types的不同参数。
在这种情况下
(1) m(K, String[]) (2) m(int, String[]) m(1, new String[]{..});
m(1)满足15.12.2.3。 阶段2:识别适用于方法调用转换的匹配方法
m(2)满足15.12.2.2。 第一阶段:确定子types适用的匹配方法
编译器在阶段1停止; 它发现m(2)是该阶段唯一适用的方法,因此selectm(2)。
在var arg情况下
(3) m(K, String...) (4) m(int, String...) m(1, str1, str2);
m(3)和m(4)满足15.12.2.4。 阶段3:确定适用的可变参数方法 。 没有比其他更具体,因此模糊。
我们可以将适用的方法分为4组:
- 适用于分类
- 适用于方法调用转换
- 可变分值,适用于分类
- vararg,通过方法调用转换适用
规范合并了第3组和第4组,并在第3阶段处理它们。因此不一致。
他们为什么这样做? 他们只是厌倦了。
另一个批评是,不应该有所有这些阶段,因为程序员不这样想。 我们应该简单地find所有适用的方法,然后select最具体的方法(有一些机制,以避免拳击/拆箱)
首先,这只是一些第一线索…可以编辑更多。
编译器总是search并select最具体的方法。 尽pipe阅读起来有点笨拙,但都是在JLS 15.12.2.5中指定的。 因此,通过调用
converter.pack(1,“Test”,“Test2”)
编译器不能确定是否将1
解散为K
或int
。 换句话说,K可以应用于任何types,所以它与int / Integer处于同一级别。
不同之处在于参数的数量和types。 考虑到new String[]{"Test", "Test2"}
是一个数组,而"Test", "Test2"
是Stringtypes的两个参数!
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,没有警告