引用与泛型不明确 [英] Reference is ambiguous with generics

查看:29
本文介绍了引用与泛型不明确的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我在这里遇到了一个非常棘手的案例,涉及泛型和方法重载.查看这个示例类:

公共类测试{公共 <T>void setValue(Parameter 参数,T 值){}公共 <T>void setValue(Parameter parameter, Field value) {}公共无效测试(){//这很完美.<T>绑定到字符串//setValue(.., String) 和 setValue(.., Field) 之间的歧义//不可能,因为 String 和 Field 不兼容参数<字符串>p1 = getP1();字段<字符串>f1 = getF1();设置值(p1,f1);//这会导致问题.<T>绑定到对象//setValue(.., Object) 和 setValue(.., Field) 之间的歧义//是可能的,因为对象和字段是兼容的参数<对象>p2 = getP2();字段<对象>f2 = getF2();设置值(p2,f2);}私有参数<字符串>getP1() {...}私有参数<对象>getP2() {...}私有字段<字符串>getF1() {...}私有字段<对象>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

在任何情况下,即使 绑定到 Object,这使得 setValue 两个方法都可以被调用,带有 Field 参数的一个似乎总是更具体.它可以在 Eclipse 中运行,只是不能与 JDK 的编译器一起使用.

更新:

像这样,它可以在 Eclipse 和 JDK 编译器中工作(当然还有 rawtypes 警告).我了解,规范中指定的规则 非常特殊,当涉及泛型时.但我觉得这很令人困惑:

 public void setValue(Parameter 参数,对象值){}//这里很容易看出这个方法比较具体公共 <T>void setValue(Parameter 参数,字段值){}

更新 2:

即使使用泛型,我也可以创建此解决方法,避免在 setValue 调用时将 类型绑定到 Object,通过添加一个额外的、明确的间接调用 setValue0.这让我觉得 T 绑定到 Object 才是真正导致这里所有麻烦的原因:

 public void setValue(Parameter 参数,T 值){}公共 <T>void setValue(Parameter parameter, Field value) {}公共 <T>void setValue0(Parameter parameter, Field value) {//这个调用在 Java 7 中没有歧义//现在在 Java 8 中是不明确的!设置值(参数,值);}公共无效测试(){参数<对象>p2 = p2();字段<对象>f2 = f2();setValue0(p2, f2);}

我在这里误解了什么吗?是否存在与此相关的已知编译器错误?或者是否有解决方法/编译器设置可以帮助我?

跟进:

对于那些感兴趣的人,我已经向 Oracle 和 Eclipse 提交了一份错误报告.Oracle 已经接受了这个bug,到目前为止,Eclipse 已经对其进行了分析并拒绝了它!看起来我的直觉是对的,这是 javac

中的一个错误

解决方案

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

非正式的直觉是,如果第一个方法处理的任何调用可以传递给另一个方法而没有编译时类型错误,那么一个方法比另一个方法更具体.">

这显然不是这里的情况.我强调了任何调用.一种方法比另一种方法更具体的特性完全取决于两种方法本身;每次调用都不会改变.

对你的问题的正式分析:m2 比 m1 更具体吗?

m1:<R>void setValue(Parameter<R>参数,R值)m2:<V>void setValue(参数参数,字段值)

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

参数<<参数 R场 V<<电阻

结果是 R=V,根据 15.12.2.7 中的推理规则

现在我们替换R并检查子类型关系

参数<:参数<V>场 V<:V

根据 4.10.2 中的子类型规则,第 2 行不成立.所以 m2 并不比 m1 更具体.

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

我建议使用不同的方法名称.重载从来都不是必需的.

<小时>

这似乎是 Eclipse 中的一个重大错误.规范非常清楚地表明在此步骤中未替换类型变量.Eclipse 显然首先进行类型变量替换,然后 检查方法特异性关系.

如果这种行为在某些示例中更明智",那么在其他示例中则不然.说,

m1: 无效检查(列表列表,T obj){打印(1");}m2:<T扩展数>无效检查(列表列表,T num){打印(2");}无效测试()检查(新数组列表<整数>(),新整数(0));

直观地",正式地根据规范,m2 比 m1 更具体,并且测试打印2".但是,如果先替换T=Integer,这两种方法就变得相同了!

<小时>

更新 2

m1:<R>void setValue(Parameter<R>参数,R值)m2:<V>void setValue(参数参数,字段值)m3:<T>void setValue2(参数参数,字段值)s4:setValue(参数,值)

这里,m1不适用于方法调用s4,所以m2是唯一的选择.

根据15.12.2.2,看m1是否适用于s4,首先进行类型推断,得出R=T的结论;然后我们检查 Ai :<Si,导致Field;<:T,这是错误的.

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

在参数化类型中

考虑下面的代码

类 PF{public void setValue(Parameter<T>参数,T值){}public void setValue(Parameter parameter, Field value) {}}无效测试()PF<对象>pf2 = 空;参数<对象>p2 = getP2();字段<对象>f2 = getF2();pf2.setValue(p2,f2);

编译没有问题.根据 4.5.2,PF 中的方法类型是 PF 中的方法,替换为 T=Object.即pf2的方法是

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

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

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

解决方案

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.

这篇关于引用与泛型不明确的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆