64

I was reading Effective Java by Joshua Bloch.

In Item 17: "Use interfaces only to define types", I came across the explanation where it is not advised to use Interfaces for storing constants. I am putting the explanation below.

"Worse, it represents a commitment: if in a future release the class is modified so that it no longer needs to use the constants, it still must implement the interface to ensure binary compatibility."

What does binary compatibility mean here?

Can someone guide me with an example in Java to show that code is binary compatible.

Ciro Santilli OurBigBook.com
  • 347,512
  • 102
  • 1,199
  • 985
Sam
  • 2,352
  • 4
  • 32
  • 45
  • http://docs.oracle.com/javase/specs/jls/se7/html/jls-13.html – Ray Cheng Feb 20 '13 at 06:15
  • 1
    indispensable reference when considering changes potentially breaking binary compatibility: https://wiki.eclipse.org/Evolving_Java-based_APIs_2 – jordanpg Nov 01 '17 at 14:33

3 Answers3

60

In short, binary compatibility means that when you change your class, you do not need to recompile classes that use it. For example, you removed or renamed a public or protected method from this class

public class Logger implements Constants {
   public Logger getLogger(String name) {
         return LogManager.getLogger(name);
   }
}

from your log-1.jar library and released a new version as log-2.jar. When users of your log-1.jar download the new version it will break their apps when they will try to use the missing getLogger(String name) method.

And if you remove Constants interface (Item 17) this will break binary compatibility either, due to the same reason.

But you can remove / rename a private or package private member of this class without breaking the binary compatibility, because external apps cannot (or should not) use it.

Tarik
  • 79,711
  • 83
  • 236
  • 349
Evgeniy Dorofeev
  • 133,369
  • 30
  • 199
  • 275
  • 11
    What amazes me is that the official documentations cannot explain their stuff with this level of simplicity. I am not sure why they want to use a similar language lawyers usually use in terms and agreements stuff. – Tarik Apr 23 '14 at 01:19
  • 8
    Richard E. Little has a very simple, nice example on his [blog](http://codefhtagn.blogspot.kr/2010/11/java-binary-compatibility-more-than.html). It is **really** showing the problem of binary compatibility rather than that of source code compatibility, as illustrated in this answer (with the renamed method). – Yann-Gaël Guéhéneuc May 09 '14 at 06:43
  • 1
    @Tarik which official documentation did you try that you can make such a bold statement? – Holger Feb 01 '22 at 11:42
37

To better understand the concept, it is interesting to see that binary compatibility does NOT imply API compatibility, nor vice versa.

API compatible but NOT binary compatible: static removal

Version 1 of library:

public class Lib {
    public static final int i = 1;
}

Client code:

public class Main {
    public static void main(String[] args) {
        if ((new Lib()).i != 1) throw null;
    }
}

Compile client code with version 1:

javac Main.java

Replace version 1 with version 2: remove static:

public class Lib {
    public final int i = 1;
}

Recompile just version 2, not the client code, and run java Main:

javac Lib.java
java Main

We get:

Exception in thread "main" java.lang.IncompatibleClassChangeError: Expected static field Lib.i
        at Main.main(Main.java:3)

This happens because even though we can write (new Lib()).i in Java for both static and member methods, it compiles to two different VM instructions depending on Lib: getstatic or getfield. This break is mentioned at JLS 7 13.4.10:

If a field that is not declared private was not declared static and is changed to be declared static, or vice versa, then a linkage error, specifically an IncompatibleClassChangeError, will result if the field is used by a pre-existing binary which expected a field of the other kind.

We would need to recompile Main with javac Main.java for it to work with the new version.

Notes:

  • calling static members from class instances like (new Lib()).i is bad style, raises a warning, and should never be done
  • this example is contrived because non-static final primitives are useless: always use static final for primitives: private final static attribute vs private final attribute
  • reflection could be used to see the difference. But reflection can also see private fields, which obviously leads to breaks which were not supposed to count as breaks, so it does not count.

Binary compatible but NOT API compatible: null pre-condition strengthening

Version 1:

public class Lib {
    /** o can be null */
    public static void method(Object o) {
        if (o != null) o.hashCode();
    }
}

Version 2:

public class Lib {
    /** o cannot be null */
    public static void method(Object o) {
        o.hashCode();
    }
}

Client:

public class Main {
    public static void main(String[] args) {
        Lib.method(null);
    }
}

This time, even if recompile Main after updating Lib, the second invocation will throw, but not the first.

This is because we changed the contract of method in a way that is not checkable at compile time by Java: before it could take null, after not anymore.

Notes:

  • the Eclipse wiki is a great source for this: https://wiki.eclipse.org/Evolving_Java-based_APIs
  • making APIs that accept null values is a questionable practice
  • it is much easier to make a change that breaks API compatibility but not binary than vice versa, since it is easy to change the internal logic of methods

C binary compatibility example

What is an application binary interface (ABI)?

Ciro Santilli OurBigBook.com
  • 347,512
  • 102
  • 1,199
  • 985
  • 2
    IMHO the first example is incorrect, because you remove `static` and it is now API incompatibility (ie., `Lib.i` in bytecode doesn't work anymore). To reason API compatibility for some code is not depend on whether clients use this API but the standard (or specification) for API compatibility. Though I agree API compatibility does not imply binary compatibility. – floating cat Feb 28 '16 at 14:11
1

If in future, we wish to change the interface that some classes are implementing (e.g., addition of some new methods).

If we add abstract methods(additional methods), then the classes (implementing the interface) must implements the additional method creating dependency constraint and cost overhead to perform the same.

To overcome this, we can add default methods in the interface.

This will remove the dependency to implement the additional methods.

We do not need to modify the implementing class to incorporate changes. This is called as Binary Compatibility.

Please refer the example below:

The interface that we are going to use

    //Interface       
    interface SampleInterface
            {
                // abstract method
                public void abstractMethod(int side);

                // default method
                default void defaultMethod() {
                   System.out.println("Default Method Block");
                }

                // static method
                static void staticMethod() {
                    System.out.println("Static Method Block");
                }
            }


//The Class that implements the above interface.

    class SampleClass implements SampleInterface
    {
        /* implementation of abstractMethod abstract method, if not implemented 
        will throw compiler error. */
        public void abstractMethod(int side)
        {System.out.println(side*side);}

        public static void main(String args[])
        {
            SampleClass sc = new SampleClass();
            sc.abstractMethod(4);

            // default method executed
            sc.defaultMethod();

            // Static method executed
            SampleInterface.staticMethod();

        }
    }

Note: For more detailed information, please refer default methods