我在这里遇到了一个非常棘手的问题,即泛型和方法重载。看看这个示例类:
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的引用不明确, 两种方法 的setValue(org.jooq.Parameter,T) 在测试和方法 的setValue(org.jooq.Parameter,org.jooq.Field) 在测试赛中
根据规范和我对Java编译器如何工作的理解,应始终选择最具体的方法:http://java.sun.com/docs/books/jls/third_edition/html/expressions.html#20448
在任何情况下,即使<T>
绑定到Object
,这使得两个setValue
方法都可以接受调用的候选者,但Field
参数的方法似乎总是更加详细一些。它适用于Eclipse,而不是JDK的编译器。
更新:
像这样,它既适用于Eclipse,也适用于JDK编译器(当然还有rawtypes警告)。我理解,the specs中指定的规则非常特殊,涉及泛型时。但我觉得这很令人困惑:
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 :
即使使用泛型,我也可以通过添加一个名为{{1的附加,明确的间接方法来创建此解决方法,避免类型<T>
在Object
调用时绑定到setValue
。 }}。这让我觉得setValue0
与T
的绑定真的是造成这里所有问题的原因:
Object
我在这里误解了什么吗?是否存在与此相关的已知编译器错误?或者是否有解决方法/编译器设置来帮助我?
对于那些感兴趣的人,我已经向Oracle和Eclipse提交了一份错误报告。 Oracle已经接受了这个bug,到目前为止,Eclipse已经对它进行了分析并拒绝了它!看起来我的直觉是正确的,这是 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);
}
答案 0 :(得分:24)
JDK是对的。第二种方法并不比第一种方法更具体。来自JLS3#15.12.2.5
“非正式的直觉是,如果第一种方法处理的任何调用可以传递给另一种方法而没有编译时类型错误,那么一种方法比另一种更具体。”
这显然不是这种情况。我强调任何调用。一种方法比另一种方法更具体的特性纯粹取决于两种方法本身;每次调用都不会改变。
对您的问题的正式分析: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
并检查子类型关系
Parameter<V> <: Parameter<V>
Field<V> <: V
根据4.10.2中的子类型规则,第二行不成立。所以m2并不比m1更具体。
此分析中 V
不是Object
;分析会考虑V
的所有可能值。
我建议使用不同的方法名称。重载永远不是必需的。
这似乎是Eclipse中的一个重要错误。规范非常清楚地表明在这一步中没有替换类型变量。 Eclipse显然首先键入变量替换,然后检查方法特异性关系。
如果在某些示例中这种行为更“明智”,则不在其他示例中。说,
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更具体,并且测试打印“2”。但是,如果首先完成替换T=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是唯一的选择。
根据15.12.2.2,为了确定m1是否适用于s4,首先进行类型推断,得出R = T的结论;然后我们检查Ai :< Si
,这会导致Field<T> <: T
,这是假的。
这与之前的分析一致 - 如果m1适用于s4,则m2处理的任何调用(与s4基本相同)都可以由m1处理,这意味着m2将比m1更具体,这是假的。
参数化类型
考虑以下代码
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>
中方法的类型是PF<T>
中具有替换T=Object
的方法。也就是说,pf2
的方法是
public void setValue(Parameter<Object> parameter, Object value)
public void setValue(Parameter<Object> parameter, Field<Object> value)
第二种方法比第一种方法更具体。
答案 1 :(得分:0)
我的猜测是编译器正在按照JLS, Section 15.12.2.5进行重载分辨率的方法。
对于本节,编译器使用strong subtyping(因此不允许任何未经检查的转换),因此,T value
变为Object value
,Field<T> value
变为Field<Object> value
。以下规则适用:
方法 m 适用于 当且仅当两者同时进行子类型化 以下条件成立:
* 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&lt;:Si。 *如果m是如上所述的通用方法,那么Ul&lt;:Bl [R1 = U1, ......,Rp = Up],1lp。
(参见子弹2)。由于Field<Object>
是Object
的子类型,因此找到了最具体的方法。字段f2
匹配您的两种方法(因为上面的子弹2)并使其模糊不清。
对于String
和Field<String>
,两者之间没有子类型关系。
PS。这是我对事物的理解,不要把它称为犹太教。
答案 2 :(得分:0)
修改:这个答案错了。看一下接受的答案。
我认为问题出现了这样的问题:编译器没有看到f2的类型(即Field)和形式参数的推断类型(即Field - &gt; Field)为同一类型。
换句话说,看起来f2(Field)的类型被认为是形式参数Field(Field)类型的子类型。由于Field与Object的子类型属于同一类型,因此编译器无法选择一种方法而不是另一种方法。
修改:让我稍微扩展一下我的陈述
这两种方法都是applicable,看起来Phase 1: Identify Matching Arity Methods Applicable by Subtyping用于决定调用哪种方法,而不是Choosing the Most Specific Method应用的规则,但由于某种原因选择第二种方法失败第一个。
阶段1 部分使用此表示法:X <: S
(X是S的子类型)。根据我对<:
的理解,X <: X
是一个有效的表达式,即<:
不严格,并且在此上下文中包含类型本身(X是X的子类型)。这解释了第1阶段的结果:从Field<Object> <: Object
和Field<Object> <: Field<Object>
开始,这两种方法都被选为候选人。
选择最具体的方法部分使用相同的表示法来表示一种方法比另一种方法更具体。段落的有趣部分以“名为m的一个固定成员方法比另一个成员更具体......”开头。除其他外,它有:
对于从1到n的所有j,Tj&lt;:Sj。
这让我觉得在我们的情况下,第二种方法必须在第一种方法上被选中,因为以下是:
Parameter<Object> <: Parameter<Object>
Field<Object> <: Object
而由于Object <: Field<Object>
为false(Object不是Field的子类型),反过来不成立。
注意:如果是String示例,第1阶段将只选择适用的唯一方法:第二个。
所以,回答你的问题:我认为这是编译器实现中的一个错误。 Eclipse有自己的增量编译器,它似乎没有这个错误。