'. '

BinaryCompatibleDefaultMethods

From APIDesign

(Difference between revisions)
Jump to: navigation, search
Line 53: Line 53:
</source>
</source>
-
Why? Since '''JDK15''' there is '''CharSequence.isEmpty()''' [[DefaultMethods|default method]]. As such, when '''javac''' processes the '''CharArrayLike''' class it doesn't know whether to select the '''ArrayLike.isEmpty()''' for delegation or the newly added method. [[SourceCompatibility]] is compromised!
+
Why? Since '''JDK15''' there is '''CharSequence.isEmpty()''' [[DefaultMethods|default method]]. As such, when '''javac''' processes the '''CharArrayLike''' class it doesn't know whether to select the '''ArrayLike.isEmpty()''' for delegation or the newly added method. [[SourceCompatibility]] is compromised! However that is not that big deal. Keeping [[SourceCompatibility]] in [[Java]] is hard. [[SourceCompatibility]] can be compromised by almost any addition. However the problem is that the above change also compromises [[BinaryCompatibility]]. Compile against '''JDK14''' [[API]]s, you'll get runtime error on '''JDK15''':
-
 
+
-
However keeping [[SourceCompatibility]] in [[Java]] is hard. It can be compromised by almost any addition. However the problem is that the above change also compromises [[BinaryCompatibility]]. Compile against, '''JDK14''' [[API]]s, you'll get runtime error on '''JDK15''':
+
<source lang="bash">
<source lang="bash">

Revision as of 06:38, 28 September 2020

DefaultMethods are useful when one desperately needs to add a method into an existing interface. However, they decrease clarity of a ProviderAPI (no, you can't disagree!). As such, don't overuse. Morever it has been recently demonstrated that adding DefaultMethods can even compromise BinaryCompatibility. Recently Emilian Bold asked me to participate in a tweeting about binary incompatibility caused by adding CharSequence.isEmpty in JDK15. An interesting case. Following code compiles on JDK8 to JDK14:

public interface ArrayLike {
    int length();
 
    default boolean isEmpty() {
        return length() == 0;
    }
}
 
final class CharArrayLike implements CharSequence, ArrayLike {
    private final char[] chars;
 
    CharArrayLike(char... chars) {
        this.chars = chars;
    }
 
    @Override
    public int length() {
        return chars.length;
    }
 
    @Override
    public char charAt(int index) {
        return chars[index];
    }
 
    @Override
    public CharSequence subSequence(int start, int end) {
        return new String(chars, start, end);
    }
 
    public static void main(String... args) {
        boolean empty = new CharArrayLike('E', 'r', 'r', 'o', 'r', '!').isEmpty();
        System.err.println("not empty: " + empty);
    }    
}

While the code compiles and runs fine with JDK14 and older, it can be no longer compiled on JDK15. It results in following error:

$ /jdk-14/bin/javac ArrayLike.java
$ /jdk-14/bin/java CharArrayLike 
not empty: false
$ /jdk-15/bin/javac ArrayLike.java 
ArrayLike.java:9: error: types CharSequence and ArrayLike are incompatible;
final class CharArrayLike implements CharSequence, ArrayLike {
      ^
  class CharArrayLike inherits unrelated defaults for isEmpty() from types CharSequence and ArrayLike
1 error

Why? Since JDK15 there is CharSequence.isEmpty() default method. As such, when javac processes the CharArrayLike class it doesn't know whether to select the ArrayLike.isEmpty() for delegation or the newly added method. SourceCompatibility is compromised! However that is not that big deal. Keeping SourceCompatibility in Java is hard. SourceCompatibility can be compromised by almost any addition. However the problem is that the above change also compromises BinaryCompatibility. Compile against JDK14 APIs, you'll get runtime error on JDK15:

$ /jdk-15/bin/javac --release 14 ArrayLike.java
$ /jdk-15/bin/java CharArrayLike 
Exception in thread "main" java.lang.IncompatibleClassChangeError: Conflicting default methods: java/lang/CharSequence.isEmpty ArrayLike.isEmpty
        at CharArrayLike.isEmpty(ArrayLike.java)
        at CharArrayLike.main(ArrayLike.java:32)

Well, that's bad. A code that used to work on previous version of Java no longer works on new one. Why? Because adding methods (even DefaultMethods) to classes that are be implemented by 3rd parties is never 100% BinaryCompatible. The fix is simple. Just add:

final class CharArrayLike implements CharSequence, ArrayLike {
    @Override
    public boolean isEmpty() {
        return ArrayLike.super.isEmpty();
    }
}
</code>
 
but such fix has to be made across all libraries. All applications using such libraries need to update to their latest versions and only then they can run on '''JDK15'''. Clearly [[DefaultMethods]] aren't a heaven sent solution, they have some cost! If you are writing an [[API]] and want to avoid your customers paying that cost, then [[APIvsSPI|separate API from SPI]] and don't mix it (especially in types that are frequently implemented by users of your [[API]]).
Personal tools
buy