5

Question from a book:

In the past (pre-Java 8), you were told that it’s bad form to add methods to an interface because it would break existing code. Now you are told that it’s okay to add new methods, provided you also supply a default implementation.

  1. How safe is that? Describe a scenario where the new stream method of the Collection interface causes legacy code to fail compilation.
  2. What about binary compatibility? Will legacy code from a JAR file still run?"

My answers are as follows but I am not quite sure about them.

  1. It's safe only if legacy code does not provide a method with the same name stream and with the same signature (e.g. in a legacy class that implements Collection). Otherwise, this old legacy code will fail compilation.
  2. I think binary compatibility is preserved, legacy code from old JAR file will still run. But I have no clear arguments at all about this.

Could anyone confirm or reject these answers, or just add some more arguments, references, or clarity to these answers?

Community
  • 1
  • 1
peter.petrov
  • 38,363
  • 16
  • 94
  • 159
  • [Related](http://stackoverflow.com/a/22618640/335858). – Sergey Kalinichenko Oct 14 '15 at 16:58
  • 1
    Doing so while preserving binary compatibility was the primary motivation of adding default methods to the language. Adding a default to an existing method is binary and source compatible; adding a new method with a default is binary and source compatible (modulo interactions with clashing methods in subclasses -- this has identical compatibility characteristics to adding a new method to a non-final class.) – Brian Goetz Oct 14 '15 at 17:22
  • 1
    While binary compatibility is preserved, there may still problems arise, when it interacts with the JRE library behavior, like in [this scenario](http://stackoverflow.com/q/26816650/2711488). You may also consider that a method might have a *compatible* signature, thus, starts to override a new `default` method without intending it… – Holger Oct 15 '15 at 10:40

1 Answers1

8
  1. The new stream() default method in Collection returns a Stream<E>, also a new type in Java 8. Legacy code will fail compilation if it contains a stream() method with the same signature, but returning something else, resulting in a clash of return types.

  2. Legacy code will continue to run as long as it's not recompiled.

First, in 1.7, set up the following:

public interface MyCollection {
    public void foo();
}

public class Legacy implements MyCollection {
    @Override
    public void foo() {
        System.out.println("foo");
    }

    public void stream() {
        System.out.println("Legacy");
    }
}

public class Main {
    public static void main(String args[]) {
        Legacy l = new Legacy();
        l.foo();
        l.stream();
    }
}

With -source 1.7 -target 1.7, this compiles and runs:

$ javac -target 1.7 -source 1.7 Legacy.java MyCollection.java Main.java
$ java Main
foo
Legacy

Now in 1.8, we add the stream method to MyCollection.

public interface MyCollection
{
    public void foo();
    public default Stream<String> stream() {
        return null;
    }
}

We compile only MyCollection in 1.8.

$ javac MyCollection.java
$ java Main
foo
Legacy

Of course we can't recompile Legacy.java any more.

$ javac Legacy.java
Legacy.java:11: error: stream() in Legacy cannot implement stream() in MyCollection
    public void stream()
                ^
  return type void is not compatible with Stream<String>
1 error
rgettman
  • 176,041
  • 30
  • 275
  • 357
  • 5
    Note that this compatibility corner-case is not new with default methods; the same issue has been present with Java 1.0 if you add a method to a superclass that is incompatible with the same-named method in some subclass. Adding methods to interfaces with defaults has the exact same compatibility characteristics as adding methods to classes. – Brian Goetz Oct 14 '15 at 19:25
  • 3
    I think, clashing methods are the smaller problem as the compiler spots them. A bigger problem occurs when the signature does not clash. With `stream()`, it’s impossible as the return type is a new class. But think about `sort(Comparator)`. You could have such a method in your custom `List` implementation before Java 8, and before Java 8, implementing it by delegating to `Collections.sort` would be reasonable. Now, `Collections.sort` delegates to `List.sort` which the pre-Java 8 method overrides without intention. The compiler won’t tell you about this problem… – Holger Oct 15 '15 at 10:47