generics是引用不明确的

generics和方法重载在这里我有一个相当棘手的情况。 看看这个例子类:

public class Test { public <T> void setValue(Parameter<T> parameter, T value) { } public <T> void setValue(Parameter<T> parameter, Field<T> value) { } public void test() { // This works perfectly. <T> is bound to String // ambiguity between setValue(.., String) and setValue(.., Field) // is impossible as String and Field are incompatible Parameter<String> p1 = getP1(); Field<String> f1 = getF1(); setValue(p1, f1); // This causes issues. <T> is bound to Object // ambiguity between setValue(.., Object) and setValue(.., Field) // is possible as Object and Field are compatible Parameter<Object> p2 = getP2(); Field<Object> f2 = getF2(); setValue(p2, f2); } private Parameter<String> getP1() {...} private Parameter<Object> getP2() {...} private Field<String> getF1() {...} private Field<Object> getF2() {...} } 

上面的例子在Eclipse(Java 1.6)中完美编译,但是不能用Ant javac命令(或者使用JDK的javac命令)编译,在第二次调用setValue出现这种错误消息:

引用setValue是不明确的,Test中的方法setValue(org.jooq.Parameter,T)和Test匹配中的方法setValue(org.jooq.Parameter,org.jooq.Field)

根据规范和我对Java编译器工作原理的理解,应该总是select最具体的方法: http : //java.sun.com/docs/books/jls/third_edition/html/expressions.html#20448

在任何情况下,即使<T>绑定到Object ,这使得两个setValue方法都成为调用的可接受候选Object ,但具有Field参数的方法总是看起来更具体。 它在Eclipse中工作,而不是在JDK的编译器中。

更新

像这样,它可以在Eclipse和JDK编译器中使用(当然,使用rawtypes警告)。 我明白,在涉及generics时,规范中指定的规则是非常特殊的。 但是我觉得这很困惑:

  public <T> void setValue(Parameter<T> parameter, Object value) { } // Here, it's easy to see that this method is more specific public <T> void setValue(Parameter<T> parameter, Field value) { } 

更新2

即使使用generics,我也可以创build这种解决方法,避免在setValue调用时将<T>types绑定到Object ,方法是添加一个名为setValue0的额外的明确间接。 这让我觉得TObject的绑定真的是这里所有的麻烦:

  public <T> void setValue(Parameter<T> parameter, T value) { } public <T> void setValue(Parameter<T> parameter, Field<T> value) { } public <T> void setValue0(Parameter<T> parameter, Field<T> value) { // This call wasn't ambiguous in Java 7 // It is now ambiguous in Java 8! setValue(parameter, value); } public void test() { Parameter<Object> p2 = p2(); Field<Object> f2 = f2(); setValue0(p2, f2); } 

我在这里误解了什么? 有没有一个与此相关的已知编译器错误? 还是有一个解决方法/编译器设置来帮助我?

跟进:

对于那些有兴趣的人,我已经向Oracle和Eclipse提交了一个错误报告。 Oracle已经接受了这个错误,到目前为止,Eclipse已经分析了它并拒绝了它! 它看起来好像我的直觉是正确的,这是一个在javac的错误

  • http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=7031404
  • https://bugs.eclipse.org/bugs/show_bug.cgi?id=340506
  • https://bugs.eclipse.org/bugs/show_bug.cgi?id=469014(Eclipse Mars中的一个新问题)

JDK是正确的。 第二种方法并不比第一种更具体。 来自JLS3#15.12.2.5

“非正式的直觉是,如果由第一个方法处理的任何调用可以传递到另一个方法而没有编译时types错误,则一种方法比另一种方法更具体。

这显然不是这种情况。 我强调了任何调用 。 一种方法的性质比另一种更具体,纯粹取决于两种方法本身; 它不会更改每个调用。

对你的问题进行forms分析:m2比m1更具体吗?

 m1: <R> void setValue(Parameter<R> parameter, R value) m2: <V> void setValue(Parameter<V> parameter, Field<V> value) 

首先,编译器需要从初始约束推断R:

 Parameter<V> << Parameter<R> Field<V> << R 

结果是R=V ,按照15.12.2.7中的推理规则

现在我们用R代替子types关系

 Parameter<V> <: Parameter<V> Field<V> <: V 

第二行不支持,按照4.10.2中的子分类规则。 所以m2不是比m1更具体。

在这个分析中V不是Object ; 分析考虑V所有可能值。

我会build议使用不同的方法名称。 重载从来不是必需的。


这似乎是Eclipse中的一个重要的错误。 规范很清楚地表明typesvariables在这一步中没有被replace。 Eclipse显然是先进行typesvariablesreplace, 然后再检查方法的特殊关系。

如果在某些例子中这样的行为更“明智”,那么在其他例子中就不是这样。 说,

 m1: <T extends Object> void check(List<T> list, T obj) { print("1"); } m2: <T extends Number> void check(List<T> list, T num) { print("2"); } void test() check( new ArrayList<Integer>(), new Integer(0) ); 

“直观地”,并且按照规范正式地,m2比m1更具体,并且testing打印“2”。 但是,如果先replaceT=Integer ,则两个方法变得相同!


更新2

 m1: <R> void setValue(Parameter<R> parameter, R value) m2: <V> void setValue(Parameter<V> parameter, Field<V> value) m3: <T> void setValue2(Parameter<T> parameter, Field<T> value) s4: setValue(parameter, value) 

在这里,m1不适用于方法调用s4,所以m2是唯一的select。

根据15.12.2.2,看m1是否适用于s4,首先进行types推断,得出R = T的结论; 那么我们检查Ai :< Si ,它导致Field<T> <: T ,这是错误的。

这与前面的分析是一致的 – 如果m1适用于s4,那么由m2处理的任何调用(基本上与s4相同)可以由m1处理,这意味着m2将比m1更具体,这是错误的。

在一个参数化的types

考虑下面的代码

 class PF<T> { public void setValue(Parameter<T> parameter, T value) { } public void setValue(Parameter<T> parameter, Field<T> value) { } } void test() PF<Object> pf2 = null; Parameter<Object> p2 = getP2(); Field<Object> f2 = getF2(); pf2.setValue(p2,f2); 

这编译没有问题。 根据4.5.2, PF<Object>中的方法的types是PF<T>中的方法,其中replaceT=Object 。 也就是说, pf2的方法是

  public void setValue(Parameter<Object> parameter, Object value) public void setValue(Parameter<Object> parameter, Field<Object> value) 

第二种方法比第一种更具体。

我的猜测是编译器正在按照JLS第15.12.2.5节的方法重载parsing。

对于本节,编译器使用强子types (因此不允许任何未经检查的转换),所以T value变为Object valueField<T> value变为Field<Object> value 。 以下规则将适用:

当且仅当以下两个条件成立时,方法m适用于子types化:

 * For 1in, either: o Ai is a subtype (§4.10) of Si (Ai <: Si) or o Ai is convertible to some type *Ci* by unchecked conversion 

(§5.1.9)和Ci <:Si。 *如果m是如上所述的通用方法,那么U1 <:Bl [R1 = U1,…,Rp = Up],1lp。

(请参阅第2项)。 由于Field<Object>Field<Object>的子types,因此find了最具体的方法。 字段f2匹配你的两种方法(因为上面的第二项),使得它不明确。

对于StringField<String> ,两者之间不存在子types关系。

PS。 这是我对事物的理解,不要引用它作为犹太教。

编辑 :这个答案是错的。 看看接受的答案。

我认为这个问题归结为:编译器没有看到f2的types(即字段)和forms参数(即字段 – >字段)的推断types为相同的types。

换句话说,看起来类似于f2(Field)被认为是forms参数Field(Field)types的子types。 由于Field与Object的子types相同,因此编译器无法select另一种方法。

编辑 :让我扩大我的发言一点

这两种方法都是适用的 ,它看起来像第一阶段:使用子types识别匹配方法来决定调用哪个方法,而不是select最具体方法的规则,但由于某种原因,第二种方法失败。

阶段1部分使用这种表示法: X <: S (X是S的子types)。 根据我对<: X <: X是一个有效的expression式的理解,即<:不是严格的,并且在这个上下文中包含了types本身(X是X的子types)。 这解释了阶段1的结果:从Field<Object> <: ObjectField<Object> <: Field<Object>select两个方法作为候选项。

select最具体的方法部分使用相同的符号来说,一种方法比另一种更具体。 以“一个名为m的固定元素成员方法比另一个成员更具体”开始的段落有趣的部分。 它包括:

对于从1到n的所有j,Tj <:Sj。

这使我认为,在我们的情况下,第二种方法必须select第一种方法,因为以下是:

  • Parameter<Object> <: Parameter<Object>
  • Field<Object> <: Object

而另一种方式由于Object <: Field<Object>为false而不成立(Object不是Field的子types)。

注意:在string示例的情况下,阶段1将简单地select适用的唯一方法:第二个。

所以,要回答你的问题:我认为这是编译器实现中的一个错误。 Eclipse有它自己的增量编译器,它似乎没有这个bug。