我在这里遇到了一个关于泛型和方法重载的非常棘手的情况。查看这个示例类:
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(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
在任何情况下,即使
绑定到 Object
,这使得两个 setValue
方法都可以接受调用,即带有 Field
的方法code> 参数似乎总是更具体。它可以在 Eclipse 中运行,只是不能与 JDK 的编译器一起运行。
更新:
像这样,它可以在 Eclipse 和 JDK 编译器中工作(当然,带有 rawtypes 警告)。据我了解,规范中指定的规则当涉及泛型时, 非常特殊。但我发现这相当令人困惑:
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:
即使使用泛型,我也可以创建此解决方法,避免类型
绑定到 Object< /code> 在 setValue
调用时,通过添加一个名为 setValue0
的附加、明确的间接寻址。这让我认为 T
到 Object
的绑定确实是造成这里所有麻烦的原因:
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已经接受了这个bug,到目前为止,Eclipse已经分析了它并拒绝了它!看来我的直觉是正确的,这是 javac
I'm having quite a tricky case here with generics and method overloading. Check out this example class:
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() {...}
}
The above example compiles perfectly in Eclipse (Java 1.6), but not with the Ant javac command (or with the JDK's javac command), where I get this sort of error message on the second invocation of setValue
:
reference to setValue is ambiguous,
both method
setValue(org.jooq.Parameter,T)
in Test and method
setValue(org.jooq.Parameter,org.jooq.Field)
in Test match
According to the specification and to my understanding of how the Java compiler works, the most specific method should always be chosen: http://java.sun.com/docs/books/jls/third_edition/html/expressions.html#20448
In any case, even if <T>
is bound to Object
, which makes both setValue
methods acceptable candidates for invocation, the one with the Field
parameter always seems to be more specific. And it works in Eclipse, just not with the JDK's compiler.
UPDATE:
Like this, it would work both in Eclipse and with the JDK compiler (with rawtypes warnings, of course). I understand, that the rules specified in the specs are quite special, when generics are involved. But I find this rather confusing:
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) {
}
UPDATE 2:
Even with generics, I can create this workaround where I avoid the type <T>
being bound to Object
at setValue
invocation time, by adding an additional, unambiguous indirection called setValue0
. This makes me think that the binding of T
to Object
is really what's causing all the trouble here:
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);
}
Am I misunderstanding something here? Is there a known compiler bug related to this? Or is there a workaround/compiler setting to help me?
Follow-Up:
For those interested, I have filed a bug report both to Oracle and Eclipse. Oracle has accepted the bug, so far, Eclipse has analysed it and rejected it! It looks as though my intuition is right and this is a bug in javac
发布评论
评论(3)
JDK是对的。第二种方法并不比第一种更具体。来自 JLS3#15.12.2.5
“非正式的直觉是,如果第一个方法处理的任何调用可以传递给另一个方法而不会出现编译时类型错误,则一个方法比另一个方法更具体。 ”
显然这里的情况并非如此。我强调了任何调用。一种方法比另一种方法更具体的属性完全取决于这两种方法本身;每次调用它都不会改变。
对你的问题进行正式分析:m2 比 m1 更具体吗?
首先,编译器需要从初始约束推断 R:
根据 15.12.2.7 中的推断规则,结果为
R=V
现在我们替换
R
并检查子类型关系第二个根据 4.10.2 中的子类型规则,该行不成立。所以m2并不比m1更具体。
在此分析中,
V
不是Object
;该分析考虑了V
的所有可能值。我建议使用不同的方法名称。超载从来都不是必要的。
这似乎是 Eclipse 中的一个重大错误。规范非常清楚地表明类型变量在此步骤中不会被替换。 Eclipse 显然首先进行类型变量替换,然后检查方法特异性关系。
如果这种行为在某些示例中更为“明智”,那么在其他示例中则不然。说
“直观地”,正式按照规范,m2 比 m1 更具体,并且测试打印“2”。但是,如果先进行替换
T=Integer
,则这两种方法将变得相同!对于Update 2
这里,m1不适用于方法调用s4,因此m2是唯一的选择。
根据15.12.2.2,为了判断m1是否适用于s4,首先进行类型推断,得出R=T的结论;然后我们检查
Ai :< Si
,导致Field <: T
,这是错误的。这与之前的分析一致——如果m1适用于s4,那么m2处理的任何调用(本质上与s4相同)都可以由m1处理,这意味着m2将比m1更具体,这是错误的。
在参数化类型中
考虑以下代码
编译没有问题。根据 4.5.2,
PF
第2个方法比第1个更具体。
JDK is right. The 2nd method is not more specific than the 1st. From JLS3#15.12.2.5
"The informal intuition is that one method is more specific than another if any invocation handled by the first method could be passed on to the other one without a compile-time type error."
This is clearly not the case here. I emphasized any invocation. The property of one method being more specific than the other purely depends on the two methods themselves; it doesn't change per invocation.
Formal analysis on your problem: is m2 more specific than m1?
First, compiler needs to infer R from the initial constraints:
The result is
R=V
, per inference rules in 15.12.2.7Now we substitute
R
and check subtype relationsThe 2nd line does not hold, per subtyping rules in 4.10.2. So m2 is not more specific than m1.
V
is notObject
in this analysis; the analysis considers all possible values ofV
.I would suggest to use different method names. Overloading is never a necessity.
This appears to be a significant bug in Eclipse. The spec quite clearly indicates that the type variables are not substituted in this step. Eclipse apparently does type variable substitution first, then check method specificity relation.
If such behavior is more "sensible" in some examples, it is not in other examples. Say,
"Intuitively", and formally per spec, m2 is more specific than m1, and the test prints "2". However, if substitution
T=Integer
is done first, the two methods become identical!for Update 2
Here, m1 is not applicable for method invocation s4, so m2 is the only choice.
Per 15.12.2.2, to see if m1 is applicable for s4, first, type inference is carried out, to the conclusion that R=T; then we check
Ai :< Si
, which leads toField<T> <: T
, which is false.This is consistent with the previous analysis - if m1 is applicable to s4, then any invocation handled by m2 (essentially same as s4) can be handled by m1, which means m2 would be more specific than m1, which is false.
in a parameterized type
Consider the following code
This compiles without problem. Per 4.5.2, the types of the methods in
PF<Object>
are methods inPF<T>
with substitutionT=Object
. That is, the methods ofpf2
areThe 2nd method is more specific than the 1st.
我的猜测是编译器正在按照 JLS,第 15.12.2.5 节。
对于本节,编译器使用强子类型< /strong> (因此不允许任何未经检查的转换),因此,
T value
变为Object value
且Field 变为
变为Object value
。 valueField
(参见第 2 点)。由于
Field
对于
String
和Field
,两者之间没有子类型关系。附言。这是我对事情的理解,请勿将其引用为犹太洁食。
My guess is that the compiler is doing an method overloading resolution as per JLS, Section 15.12.2.5.
For this Section, the compiler uses strong subtyping (thus not allowing any unchecked conversion), so,
T value
becomesObject value
andField<T> value
becomesField<Object> value
. The following rules will apply:(Refer to bullet 2). Since
Field<Object>
is a subtype ofObject
then the most specific method is found. Fieldf2
matches both methods of yours (because of bullet 2 above) and makes it ambiguous.For
String
andField<String>
, there is no subtype relationship between the two.PS. This is my understanding of things, don't quote it as kosher.
编辑:这个答案是错误的。看看接受的答案。
我认为问题归结为:编译器没有将 f2 的类型(即 Field)和形式参数的推断类型(即 Field -> Field)视为同一类型。
换句话说,看起来f2(Field)的类型被认为是形参Field(Field)类型的子类型。由于 Field 是同一类型的 Object 子类型,因此编译器无法选择一种方法而不是另一种方法。
编辑:让我稍微扩展一下我的陈述
这两种方法都是适用,它看起来像 阶段 1:通过子类型识别适用的匹配数量方法 用于决定调用哪个方法以及来自 选择最具体的方法已应用,但由于某种原因未能选择第二种方法而不是第一种方法。
阶段 1 部分使用以下表示法:
X <: S
(X 是 S 的子类型)。根据我对<:
的理解,X <: X
是一个有效的表达式,即<:
并不严格,包括在此上下文中类型本身(X 是 X 的子类型)。这解释了第一阶段的结果:两种方法都被选为候选方法,因为Field
选择最具体的方法部分使用相同的符号来表示一种方法比另一种方法更具体。有趣的部分是以“一个名为 m 的固定数量成员方法比另一个成员更具体......”开头的段落。除其他外,它具有:
这让我认为在我们的例子中,必须选择第二个方法而不是第一个方法,因为以下内容成立:
Parameter; <: 参数<对象>
字段<对象> <: Object
而反过来则不成立,因为
Object <: Field为 false(Object 不是 Field 的子类型)。
注意:对于字符串示例,第一阶段将简单地选择唯一适用的方法:第二个。
所以,回答你的问题:我认为这是编译器实现中的一个错误。 Eclipse 有它自己的增量编译器,看起来没有这个错误。
Edit: This answer is wrong. Take a look at accepted answer.
I think the issue comes down this: compiler does not see the type of f2 (i.e. Field) and the inferred type of formal parameter (i.e. Field -> Field) as the same type.
In other words, it looks like type of f2 (Field) is considered to be a subtype of the type of formal parameter Field (Field). Since Field is at the same type a subtype of Object, compiler cannot pick one method over another.
Edit: Let me expand my statement a bit
Both methods are applicable and it looks like the Phase 1: Identify Matching Arity Methods Applicable by Subtyping is used to decide which method to call and than rules from Choosing the Most Specific Method applied, but failed for some reason to pick second method over first one.
Phase 1 section uses this notation:
X <: S
(X is subtype of S). Based on my understanding of<:
,X <: X
is a valid expression, i.e. the<:
is not strict and includes the type itself (X is subtype of X) in this context. This explains the result of Phase 1: both methods are picked as candidates, sinceField<Object> <: Object
andField<Object> <: Field<Object>
.Choosing the Most Specific Method section uses same notation to say that one method is more specific than another. The interesting part the paragraph that starts with "One fixed-arity member method named m is more specific than another member...". It has, among other things:
This makes me think that in our case second method must be chosen over the first one, because following holds:
Parameter<Object> <: Parameter<Object>
Field<Object> <: Object
while the other way around does not hold due to
Object <: Field<Object>
being false (Object is not a subtype of Field).Note: In case of String examples, Phase 1 will simply pick the only method applicable: the second one.
So, to answer your questions: I think this is a bug in compiler implementation. Eclipse has it is own incremental compiler which does not have this bug it seems.