Here's a simple class that demonstrates the issue:
package com.mimvista.debug;
public class DefaultCollisionTest {
public static interface Interface1 {
public String getName();
}
public static interface Interface2 {
public default String getName() { return "Mr. 2"; };
}
public static <X extends Interface1&Interface2> String extractName(X target) {
return target.getName();
}
}
Eclipse (Neon 2) happily compiles this class while javac (JDK 1.8.0_121) spits out the following compile error:
$ javac src/com/mimvista/debug/DefaultCollisionTest.java
src\com\mimvista\debug\DefaultCollisionTest.java:13: error: class INT#1 inherits abstract and default for getName() from types Interface2 and Interface1
public static <X extends Interface1&Interface2> String extractName(X target) {
^
where INT#1 is an intersection type:
INT#1 extends Object,Interface1,Interface2
1 error
I believe that Eclipse is correct in this case but I'm not totally sure. Based on my understanding of the "inherits abstract and default" error, I think it should only be generated when compiling an actual declared class that implements those two interfaces. It seems like javac may be generating an intermediate class under-the-hood to deal with that generic signature and erroneously subjecting it to the default method collision test?
I would say that this is a Javac bug, or at least it should be.
Looks like the implementors of Javac took a shortcut and reused the code for creating an interface in the implementation of generic boundary. IOW, Javac treats
<X extends I1&I2>
as if it wasinterface X extends I1, I2
.In reality, however,
<X extends I1&I2>
is different. It just means thatX
has the methods of bothI1
andI2
, but says nothing about the methods' implementations. Therefore absence or presence ofdefault
implementation should be irrelevant.Unfortunately, as @slim says, the goal is to pass JDK's compiler, so Javac has the last word. Submit a bug report, maybe?
Eclipse is right.
I have not found this javac bug in the Java Bug Database and therefore reported it: JDK-8186643
Better explanation by Stephan Herrmann (see his comment below):
A class that implements multiple interfaces of the same method can be compiled with both compilers, even if the method of one interface has a default implementation. The class can be referenced as
<T extends I1 & I2>
as long as neither I1 nor I2 has a default implementation for a equally named method. Only if one of the two interfaces has a default implementation javac fails.In case of ambiguity which implementation should apply, the error should already occur when defining a class, not when the class is referred as
<T extends ...>
(see JLS 4.9. Intersection Types).See following example which works with
<T extends I1 & I2>
and<T extends IDefault>
, but fails with<T extends I1 & IDefault>
and javac:Javac is correct according to JLS 9.4.1.3. Interfaces > Inheriting Methods with Override-Equivalent Signatures:
The small print explains:
Also compare with 8.4.8.4. Classes > Inheriting Methods with Override-Equivalent Signatures:
In even plainer words: the assumption is that the two interfaces are logically unrelated and both specify some kind of a behaviour contract. Therefore it's not safe to assume that the default implementation in
Interface2
is a valid fulfilment of the contract ofInterface1
. It's safer to throw an error and let the developer sort it out.I didn't find a place in the JLS where it would exactly tackle your case, but I think the error is in the gist of the above specifications - you declare that
extractName()
should take an object that implements bothInterface1
andInterface2
. But for such an object it would only be valid if "there exists an abstract method declared in a superclass of C and inherited by C that is override-equivalent with the two methods". Your generic declaration does not specify anything about the superclass ofX
, so the compiler treats it as a "abstract-default" clash.As I understand it, the question is about passing an object of an already compiled class as a parameter. Since you cannot call the
extractName(X)
method with an abstract class or an interface, the argument object must have it'sgetName()
method resolved and unambiguous. Java uses a late binding for resolving which overridden method is called at runtime, so I would agree with BonusLord that the method could be correctly compiled and run even ifjavac
throws the error.