JDK-7034913 : 15.12.2.5: Resolution of call to abstract methods is nondeterministic
  • Type: Bug
  • Component: specification
  • Sub-Component: language
  • Affected Version: 5.0,7,8
  • Priority: P4
  • Status: Closed
  • Resolution: Fixed
  • Submitted: 2011-04-07
  • Updated: 2018-08-03
  • Resolved: 2016-10-13
The Version table provides details related to the release that this issue/RFE will be addressed.

Unresolved : Release in which this issue/RFE will be addressed.
Resolved: Release in which this issue/RFE has been resolved.
Fixed : Release in which this issue/RFE has been fixed. The release containing this fix may be available for download as an Early Access Release or a General Availability Release.

To download the current JDK release, click here.
JDK 9
9Fixed
Related Reports
Relates :  
Relates :  
Relates :  
Relates :  
Relates :  
Description
It is possible for more than one inherited abstract method of a class to have the same signature, and thus more than one method may be equally valid as the "most specific" choice for an invocation.  In this situation, the following from JLS 3 15.12.2.5 applies:

"Otherwise, if all the maximally specific methods are declared abstract, and the signatures of all of the maximally specific methods have the same erasure (��4.6), then the most specific method is chosen arbitrarily among the subset of the maximally specific methods that have the most specific return type."

(How to determine "the most specific return type" is unclear, but the assumption that it exists suggests that the intent must be to find the methods that are return-type-substitutable for all the candidates.)

In the following example, both methods have "the most specific return type", and so both are equally valid, and the specified choice is arbitrary.  This is unsatisfactory, because the nondeterministic choice can be observed.

public class AbstractGeneric {

interface A {
 <T> Iterable<T> foo();
}

interface B {
 Iterable foo();
}

abstract class C implements A, B {
 void test() {
   Iterable<String> l1 = ((A) this).foo(); // legal
   Iterable<String> l2 = ((B) this).foo(); // unchecked conversion
   Iterable<String> l3 = foo(); // unchecked conversion?
 }
}

}

I think that the reasonable thing for 15.12.2.5 to say is that either only the erased one(s) or the erasures of all of them (as we do with SAM types) should be considered as candidates.

To help get this right, here are some guarantees that we have from 8.4 on a maximal set of override-equivalent signatures in a class, assuming that they are all abstract:
- One of the methods is return-type-substitutable for the others (its return type is either a subtype, a raw equivalent of a subtype, or the erased type variable upper bound of every other return type)
- If there is a pair of return types R1 and R2 such that R1 = |R2| and R1 is "most specific", then R2 is also "most specific"
- Every method has the same erased signature
- If the method signatures are not "the same," then they can be divided into two groups of signatures that are, within the group, "the same."  The first group is defined by the generic form of the signature, and the second group by the erased form.

Given the definition of "most specific", however, I don't think that we can guarantee we're looking at the maximal set when we reach the end of 15.12.2.5. That is, it may be that some elements in the maximal set are strictly more specific than others (after type argument substitution).  The first item above only applies to a maximal set, while the others apply equally to any subset.

Comments
Proposed text for 15.12.2.5 (after modifications from JDK-7028828): Otherwise, if all the maximally specific methods are abstract or default, the signatures of these methods are override-equivalent, ***the*** declarations of these methods have the same erased parameter types (4.6), ***and at least one maximally specific method is _preferred_ according to the rules below,*** then the most specific method is chosen arbitrarily among the subset of the maximally specific methods ***that are preferred***. In this case, the most specific method is considered to be abstract. Also, the most specific method is considered to throw ***only certain exception types, as described below.*** ***A maximally specific method is _preferred_ if it has:*** ***1. a signature that is a subsignature of every maximally specific method's signature; and*** ***2. a return type R (possibly 'void'), where either R is the same as every maximally specific method's return type, or R is a reference type and is a subtype of every maximally specific method's return type (after adapting for any type parameters (8.4.4) if the two methods have the same signature)*** ***If no preferred method exists according to the above rules, then a maximally specific method is _preferred_ if it:*** ***1. has a signature that is a subsignature of every maximally specific method's signature; and*** ***2. is return-type-substitutable (8.4.5) for every maximally specific method*** ***The thrown exception types are derived from the throws clauses of the maximally specific methods. If the most specific method is generic, these clauses are first adapted to the type parameters of the most specific method (��8.4.4). If the most specific method is not generic but at least one maximally specific method is generic, these clauses are first erased. Then, the thrown exception types include every type, E, which satisfies the following constraints:*** - ***E is mentioned in one of the throws clauses.*** - ***For each throws clause, E is a subtype of some type named in that clause.*** ***[Note:] These rules for deriving a single method type from a group of overloaded methods are also used to identify the function type of a functional interface (9.9).*** [May also want to add a cross-reference from 9.9 to 15.12.2.5.] ---------- Update to 9.9: Let m be a method in M with: 1. a signature that is a subsignature of every method's signature in M; and 2. a return type ***R (possibly 'void'), where either R is the same as every method's return type in M, or R is a reference type and*** is a subtype of every method's return type in M (after adapting for any type parameters (��8.4.4) ***if the two methods have the same signature***).
06-10-2016

Vicente pointed out that the subtype check allows things like int <: long, which is not what we want. I'm updating the spec text to address this.
06-10-2016

It might be appropriate to combine the 15.12.2.5 rules and the 9.9 rules (which, as I pointed out, do essentially the same thing) into a single definition, but it's not obvious to me where to put it, and it doesn't seem worth it for the slight reduction in redundancy.
01-10-2016

None of the above examples exploit 'throws' clauses, but they might also be a source of nondeterminism and/or confusion in JLS 8. Specific concerns, addressed in the new spec text: - Should *any* qualifying exception type be included, or only those that are declared to be thrown by the most specific method? - If one method throws IOException and another throws Exception, shouldn't the IOException be thrown? (javac does this) - How do you compare type variables with disjoint scopes? (need adaptation, per 8.4.4; javac doesn't do this)
30-09-2016

May want to resolve this and JDK-7028828 at the same time.
17-12-2015

PUBLIC COMMENTS Another test case, this one for two abstract methods with unrelated return types: public class TwoMethods { static interface Merge<T> { java.io.Serializable m(Cloneable arg); T m(T arg); } static class C implements Merge<Cloneable> { public Object[] m(Cloneable arg) { return new Object[0]; } } public static void main(String... arg) { Merge<Cloneable> merge = new C(); Cloneable c = merge.m(new Object[0]); // javac 7 error java.io.Serializable s = merge.m(new Object[0]); // javac 7 error } } In this situation, we could arguably take the glb of the return types. But that doesn't generalize to all return types (can't intersect two classes, for example), so the compiler's behavior of reporting ambiguity is probably the right thing to specify.
01-02-2012

PUBLIC COMMENTS Another example, this one demonstrating an nondeterministic _error_: public class AbstractGeneric2 { interface A { <T> T foo(T arg); } interface B { <T> Object foo(T arg); } abstract class C implements A, B { void test() { String s1 = ((A) this).foo("x"); // legal String s2 = ((B) this).foo("x"); // error String s3 = foo("x"); // error? } } } Seems like the right answer is to take a priority-based approach: - First, try to find a best subtype. - Second, try to find a best subtype after unchecked conversion. - Third, try to find a best erased type. The third step may seem redundant -- R1=|R2| implies R2<:R1 -- but it may be the only solution when there are three or more methods. For example, R1=|R2| and R1=|R3|, but R2 </: R3 and R3 </: R2; the only "best" choice is the erasure, R1.
01-06-2011

PUBLIC COMMENTS One thing to add: the guarantees about methods in a declaration do not necessarily translate into guarantees about methods in an *instantiation*, because type arguments can change method signatures and return types. Example: public interface Foo<T> { String m(String s); int m(T o); } public class Bar<T> implements Foo<T> { String m(String s) { return s; } int m(T o) { return o.hashCode(); } } Bar<String> bs = new Bar<String>(); String s1 = bs.m("abc"); String s2 = ((Foo<String>) bs).m("abc"); At this point, the strategy in 15.12.2.5 falls apart. There is no most-specific return type among the methods with the same signature. The choice to prefer a concrete method over an abstract one is arbitrary when both are declared in the same class -- I think the intent is to prefer a class method over an inherited interface method.
22-04-2011