引用与泛型不明确

发布于 2024-10-24 06:51:16 字数 3839 浏览 2 评论 0 原文

我在这里遇到了一个关于泛型和方法重载的非常棘手的情况。查看这个示例类:

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 的附加、明确的间接寻址。这让我认为 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已经接受了这个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

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

绮筵 2024-10-31 06:51:16

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

根据 15.12.2.7 中的推断规则,结果为 R=V

现在我们替换 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,则这两种方法将变得相同!


对于Update 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,这是错误的。

这与之前的分析一致——如果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中方法的类型是 PF 中替换为 T=Object 的方法。也就是说,pf2的方法是

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

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

第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?

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

First, compiler needs to infer R from the initial constraints:

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

The result is R=V, per inference rules in 15.12.2.7

Now we substitute R and check subtype relations

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

The 2nd line does not hold, per subtyping rules in 4.10.2. So m2 is not more specific than m1.

V is not Object in this analysis; the analysis considers all possible values of V.

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,

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) );

"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

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)

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 to Field<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

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);

This compiles without problem. Per 4.5.2, the types of the methods in PF<Object> are methods in PF<T> with substitution T=Object. That is, the methods of pf2 are

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

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

The 2nd method is more specific than the 1st.

溺孤伤于心 2024-10-31 06:51:16

我的猜测是编译器正在按照 JLS,第 15.12.2.5 节

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

方法m适用于
子类型当且仅当两者
满足以下条件:

* 对于 1 英寸,可以:
      o Ai 是 Si 的亚型 (§4.10) (Ai <: Si) 或
      o Ai 可通过未经检查的转换转换为某种类型 *Ci*

(§5.1.9),且 Ci <:Si。
* 如果 m 是如上所述的通用方法,则 Ul <: Bl[R1 = U1,
...,Rp = 向上],1lp。

(参见第 2 点)。由于 FieldObject 的子类型,因此会找到最具体的方法。字段 f2 与您的两种方法相匹配(因为上面的第 2 点)并使其变得不明确。

对于StringField,两者之间没有子类型关系。

附言。这是我对事情的理解,请勿将其引用为犹太洁食。

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 becomes Object value and Field<T> value becomes Field<Object> value. The following rules will apply:

The method m is applicable by
subtyping if and only if both of the
following conditions hold:

* 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), and Ci <: Si.
* If m is a generic method as described above then Ul <: Bl[R1 = U1,
..., Rp = Up], 1lp.

(Refer to bullet 2). Since Field<Object> is a subtype of Object then the most specific method is found. Field f2 matches both methods of yours (because of bullet 2 above) and makes it ambiguous.

For String and Field<String>, there is no subtype relationship between the two.

PS. This is my understanding of things, don't quote it as kosher.

挥剑断情 2024-10-31 06:51:16

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

我认为问题归结为:编译器没有将 f2 的类型(即 Field)和形式参数的推断类型(即 Field -> Field)视为同一类型。

换句话说,看起来f2(Field)的类型被认为是形参Field(Field)类型的子类型。由于 Field 是同一类型的 Object 子类型,因此编译器无法选择一种方法而不是另一种方法。

编辑:让我稍微扩展一下我的陈述

这两种方法都是适用,它看起来像 阶段 1:通过子类型识别适用的匹配数量方法 用于决定调用哪个方法以及来自 选择最具体的方法已应用,但由于某种原因未能选择第二种方法而不是第一种方法。

阶段 1 部分使用以下表示法:X <: S(X 是 S 的子类型)。根据我对 <: 的理解,X <: X 是一个有效的表达式,即 <: 并不严格,包括在此上下文中类型本身(X 是 X 的子类型)。这解释了第一阶段的结果:两种方法都被选为候选方法,因为 Field<: 对象 和 Field<:字段<对象>。

选择最具体的方法部分使用相同的符号来表示一种方法比另一种方法更具体。有趣的部分是以“一个名为 m 的固定数量成员方法比另一个成员更具体......”开头的段落。除其他外,它具有:

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

这让我认为在我们的例子中,必须选择第二个方法而不是第一个方法,因为以下内容成立:

  • 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, since Field<Object> <: Object and Field<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:

For all j from 1 to n, Tj <: Sj.

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.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文