参考文献对泛型不明确

Luk*_*der 36 java eclipse generics overloading javac

我在这里有一个非常棘手的案例,有泛型和方法重载.看看这个示例类:

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() {...}
}
Run Code Online (Sandbox Code Playgroud)

上面的例子在Eclipse(Java 1.6)中完美编译,但没有使用Ant javac命令(或使用JDK的javac命令),在第二次调用时我得到这种错误消息setValue:

对setValue的引用是不明确的,Test中的方法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警告).据我所知,当涉及泛型时,规范中指定的规则非常特殊.但我觉得这很令人困惑:

    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) {
    }
Run Code Online (Sandbox Code Playgroud)

更新2:

即使使用泛型,我也可以创建这种解决方法,通过添加一个额外的,明确的间接调用<T>来避免ObjectsetValue调用时绑定的类型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);
    }
Run Code Online (Sandbox Code Playgroud)

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

跟进:

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

irr*_*ble 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) 
Run Code Online (Sandbox Code Playgroud)

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

Parameter<V>   <<   Parameter<R>
Field<V>       <<   R
Run Code Online (Sandbox Code Playgroud)

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

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

Parameter<V>   <:   Parameter<V>
Field<V>       <:   V
Run Code Online (Sandbox Code Playgroud)

根据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) );
Run Code Online (Sandbox Code Playgroud)

"直观地",并且正式按照规范,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)
Run Code Online (Sandbox Code Playgroud)

这里,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);
Run Code Online (Sandbox Code Playgroud)

这编译没有问题.根据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) 
Run Code Online (Sandbox Code Playgroud)

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

  • @irreputable:你的补充是有道理的.所有这些只是一般的噩梦.我仍然认为Eclipse的编译器在这种情况下表现得"更好",因为它可能包括一个额外的(根据规范的错误)消歧规则,考虑到```被绑定为`T`时存在歧义.规格.但这是我非常非正式和直观的观点,作为编译器 - 反专家:-). (2认同)