开发者

Reference is ambiguous with generics

开发者 https://www.devze.com 2023-02-17 16:06 出处:网络
I\'m having quite a tricky case here with generics and method overloading. Check out this example class:

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 thi开发者_StackOverflow中文版s 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

  • http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=7031404
  • https://bugs.eclipse.org/bugs/show_bug.cgi?id=340506
  • https://bugs.eclipse.org/bugs/show_bug.cgi?id=469014 (a new issue in Eclipse Mars)


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.


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.


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.

0

精彩评论

暂无评论...
验证码 换一张
取 消