When compiling a Client
, which uses some implementation of interface I
(e.g. O
), the classfile for I
must also be present on the classpath. What is strange, this is only a case for javac
, as Eclipse compiler (ECJ) does not require I
for compilation.
What makes JDK requires supertypes for compilation, where ECJ compiles just fine?
It is not default methods, as was commented in the bug report, and the compatibility guide also agrees:
When compiling a class against another class implementing an interface which is defined in yet another class file, such class file (where interface is defined) must be available in the class path used by javac during compilation. This is a new requirement as of JDK 8 - a failure to do so will result in a compilation error.
UPDATE:
- Similar question: Java 8 interface/class loader changes?
- It doesn't matter if
I.doit()
isdefault
or plain abstract method, the behavior is the same - It of course matters whether
I.doit()
is overridden inO
or not; if not overriden, then ECJ also reaches toI
for the definition ofdoit()
The interface (api/a/I.java
):
package a;
public interface I {
default void doit() {
System.out.println("In I");
}
}
Implementation (impl/b/O.java
):
package b;
public class O implements a.I {
public void doit() {
System.out.println("In O");
}
}
Client (client/c/Client.java
):
package c;
import b.O;
public class Client {
public void test() {
O o = new O();
o.doit();
}
public static void main(String[] args) {
new Client().test();
}
}
A Makefile
:
# bug report:
# Javac requires interface on classpath when using impl
# https://bugs.openjdk.java.net/browse/JDK-8055048
#
# compatibility guide:
# http://www.oracle.com/technetwork/java/javase/8-compatibility-guide-2156366.html
# (Synopsis: Interfaces need to be present when compiling against their implementations)
#
# ECJ downloaded from:
# http://central.maven.org/maven2/org/eclipse/jdt/core/compiler/ecj/4.6.1/ecj-4.6.1.jar
ifeq (${V}, ecj)
JC := java -jar ecj-4.6.1.jar -8
else
JC := javac -source 1.8 -target 1.8 -implicit:none
endif
rebuild: clean lib client
lib: api/a/I.class impl/b/O.class
client: lib client/c/Client.class
clean:
rm -f api/a/I.class impl/b/O.class client/c/Client.class
%.class: %.java
${JC} ${OPT} $<
impl/b/O.class: OPT = -cp api
client/c/Client.class: OPT = -cp impl
A log:
$ make V=ecj rebuild
rm -f api/a/I.class impl/b/O.class client/c/Client.class
java -jar ecj-4.6.1.jar -8 api/a/I.java
java -jar ecj-4.6.1.jar -8 -cp api impl/b/O.java
java -jar ecj-4.6.1.jar -8 -cp impl client/c/Client.java
$ make rebuild
rm -f api/a/I.class impl/b/O.class client/c/Client.class
javac -source 1.8 -target 1.8 -implicit:none api/a/I.java
javac -source 1.8 -target 1.8 -implicit:none -cp api impl/b/O.java
javac -source 1.8 -target 1.8 -implicit:none -cp impl client/c/Client.java
client/c/Client.java:8: error: cannot access I
o.doit();
^
class file for a.I not found
1 error
make: *** [client/c/Client.class] Error 1