8306854: javac with '-source 7' should honor default methods in implemented Java 8 interfaces
Volker Simonis
volker.simonis at gmail.com
Wed Apr 26 14:10:26 UTC 2023
On Tue, Apr 25, 2023 at 9:17 PM Remi Forax <forax at univ-mlv.fr> wrote:
>
> ----- Original Message -----
> > From: "Alex Buckley" <alex.buckley at oracle.com>
> > To: "compiler-dev" <compiler-dev at openjdk.java.net>
> > Sent: Tuesday, April 25, 2023 7:09:41 PM
> > Subject: Re: 8306854: javac with '-source 7' should honor default methods in implemented Java 8 interfaces
>
> > On 4/25/2023 8:33 AM, Volker Simonis wrote:
> >> ```
> >> interface A {
> >> void foo();
> >> }
> >> ```
> >>
> >> ```
> >> interface B extends A {
> >> @Override default void foo() { }
> >> }
> >> ```
> >>
> >> ```
> >> class C implements B { }
> >> ```
> >>
> >> If we compile `A.java` and `B.java` with `javac -source 8` and
> >> `C.java` with `-source 7` we will get the following error:
> >> ```
> >> $ javac -source 8 A.java
> >> $ javac -source 8 B.java
> >> $ javac -source 7 C.java
> >> C.java:1: error: C is not abstract and does not override abstract
> >> method foo() in A
> >> class C implements B { }
> >> ```
> >>
> >> I think this is wrong, because `foo()` is implemented as a default
> >> method in `B`.
> >
> > The Java language circa JLS7 does not have default methods, so the
> > membership of C cannot possibly involve a default method inherited from
> > B. The Java language is backward compatible, not forward compatible.
> >
> > A Java compiler that adheres to JLS7 (`-source 7`) is right to reject
> > C.java -- either with the error given above, or with a more
> > context-aware error along the lines of "C is written for Java 7, cannot
> > inherit default method from B".
> >
> >> The following, slightly simpler example works perfectly fine, although
> >> it also depends on a default method in an implemented interface:
> >>
> >> ```
> >> interface D {
> >> default void bar() { }
> >> }
> >> ```
> >>
> >> ```
> >> class E implements D { }
> >> ```
> >>
> >> ```
> >> $ javac -source 8 D.java
> >> $ javac -source 7 E.java
> >> ```
> >>
> >> In the second example, `javac` happily accepts the default
> >> implementation of `bar()` from interface `D`.
> >
> > It's hard to see how javac is processing class E, so I added a method
> > declaration `@Override public void bar() {}` to E and `javac -source 7
> > E.java` accepted the code. That's improper, because in Java 7 there is
> > no such thing as a default method in D to override from E.
> >
> > I then added a method invocation `D.super.bar();` as the body of E's bar
> > method. In JLS8, this means "invoke my superinterface's default method".
> > However, in JLS7, it means "invoke my enclosing class's method", but D
> > isn't an enclosing class, so it's illegal (see
> > https://docs.oracle.com/javase/specs/jls/se7/html/jls-15.html#jls-15.12.1-100-D)
> > -- yet `javac -source 7 E.java` accepts it anyway. Highly improper on
> > javac's part.
> >
> > Alex
>
> Alex, Java 8 adds a lot of default methods to the interfaces of the collection API, so javac8 -source 7 could not stop each time it sees a default methods, otherwise a lot of code will never have compiled. Which means that javac7 -source 7 and javac8 -source 7 have to have different behaviors.
>
> It was decided that javac8 -source 7 should skip default methods because it works well with the collection API even if it does not work that well in other cases as the one provided by Volker.
What do you mean by "skip". 'javac8 -source 7' can make use of default
methods quite well. E.g. in the following example:
```
interface D {
default void bar() { }
}
```
```
class F implements D {
void foo() { bar(); }
}
```
```
$ javac -source 8 D.java
$ javac -source 7 F.java
```
javac will add an invokevirtual call to `bar()` into `F::foo()` (it
basically generated the same code like with 'source 8').
The problem is that it doesn't recognize default methods in all cases
as my first example demonstrated.
> Volker, I believe the behavior you see is not a bug, it's how javac should behave. But i don't think this behavior is specified somewhere, that why ecj has a different behavior. Moreover, javac never behaves differently depending on a specific classfile version of a specific classfile to preserve the sanity of the people trying to debug issues involving several versions.
I can accept that the current behavior isn't considered a bug (because
without proper specification, how can we know what the correct
behavior is :) But let me ask the other way round: would it be a bug
if 'javac -source 7' would also handle my first example without
emitting an error? The fix I've proposed is minimal, only affects
'javac -source 7' and doesn't seem to do any harm. Or am I missing
some bigger problems?
Thank you and best regards,
Volker
>
> regards,
> Rémi
More information about the compiler-dev
mailing list