Java8: Why Is It Forbidden to Define a Default Method for a Method from Java.Lang.Object

Java8: Why is it forbidden to define a default method for a method from java.lang.Object

This is yet another of those language design issues that seems "obviously a good idea" until you start digging and you realize that its actually a bad idea.

This mail has a lot on the subject (and on other subjects too.) There were several design forces that converged to bring us to the current design:

  • The desire to keep the inheritance model simple;
  • The fact that once you look past the obvious examples (e.g., turning AbstractList into an interface), you realize that inheriting equals/hashCode/toString is strongly tied to single inheritance and state, and interfaces are multiply inherited and stateless;
  • That it potentially opened the door to some surprising behaviors.

You've already touched on the "keep it simple" goal; the inheritance and conflict-resolution rules are designed to be very simple (classes win over interfaces, derived interfaces win over superinterfaces, and any other conflicts are resolved by the implementing class.) Of course these rules could be tweaked to make an exception, but I think you'll find when you start pulling on that string, that the incremental complexity is not as small as you might think.

Of course, there's some degree of benefit that would justify more complexity, but in this case it's not there. The methods we're talking about here are equals, hashCode, and toString. These methods are all intrinsically about object state, and it is the class that owns the state, not the interface, who is in the best position to determine what equality means for that class (especially as the contract for equality is quite strong; see Effective Java for some surprising consequences); interface writers are just too far removed.

It's easy to pull out the AbstractList example; it would be lovely if we could get rid of AbstractList and put the behavior into the List interface. But once you move beyond this obvious example, there are not many other good examples to be found. At root, AbstractList is designed for single inheritance. But interfaces must be designed for multiple inheritance.

Further, imagine you are writing this class:

class Foo implements com.libraryA.Bar, com.libraryB.Moo { 
// Implementation of Foo, that does NOT override equals
}

The Foo writer looks at the supertypes, sees no implementation of equals, and concludes that to get reference equality, all he need do is inherit equals from Object. Then, next week, the library maintainer for Bar "helpfully" adds a default equals implementation. Ooops! Now the semantics of Foo have been broken by an interface in another maintenance domain "helpfully" adding a default for a common method.

Defaults are supposed to be defaults. Adding a default to an interface where there was none (anywhere in the hierarchy) should not affect the semantics of concrete implementing classes. But if defaults could "override" Object methods, that wouldn't be true.

So, while it seems like a harmless feature, it is in fact quite harmful: it adds a lot of complexity for little incremental expressivity, and it makes it far too easy for well-intentioned, harmless-looking changes to separately compiled interfaces to undermine the intended semantics of implementing classes.

Why we cannot add default for method in class

The default keyword has nothing to do with default 'package private' access that derives from not specifying the access. The default keyword only applies to interfaces (and not just to functional interfaces), to supply a default implementation in the interface. This is a feature introduced in Java 8 to allow for easier interface evolution.

Overloading the default keyword to mean 'package private' access when used in classes would only be confusing and serve no real value, as the same is achieved by not specifying access.

default method in interfaces

If you mandate all the implementing classes to override the new method, then it should (if there is no valid default implementation) not be a default method.

But, the approach you have said in which to create a new interface that extends the existing one and making the classes that wish to override the new method(s) by changing the interface type they implement will be problematic since the new method is part of the new interface, you cannot access it when you have the parent/base interface type.

Example:

Existing code:

interface Base {
void m1();
}
class A implements Base {
@Override
public void m1() {
....
}
}
class B implements Base {
@Override
public void m1() {
....
}
}

You create the following interface

interface ExtendedBase extends Base {
void m2();
}

Only class A wants to implement m2. So, it becomes

class A implements ExtendedBase {
@Override
public void m1() {
....
}
@Override
public void m2() {
....
}
}

All is good so far.

When you have a method that takes an object of type Base, you can only call m1 on it (irrespective of you pass object of type A or B)

void someMethod(Base base) {
base.m1();
//base.m2(); won't work
}

To actually make use of m2 elsewhere, you need to change Base to ExtendedBase which would mean that you can no longer pass a B to it. So, you have made all classes implement m2 anyway.

Why, after compiling an interface, default method modifier is gone from javap -v?

Surely looks like a javap bug, see this defect. I've encountered a few more of these javap issues when new features where added, just FYI.

Instantiating interfaces having default methods

There is nothing wrong with the statement:

I i = new I() {};

It simply instantiates an anonymous class that implements the I interface. Since the I interface has only default methods, an empty body would be sufficient to implement it, if not for the problem with the toString() method.

JLS 9.4.1.2 states that an interface cannot have a default implementation of the toString() method :

It is a compile-time error if a default method is override-equivalent with a non-private method of the class Object, because any class implementing the interface will inherit its own implementation of the method.

The prohibition against declaring one of the Object methods as a default method may be surprising. There are, after all, cases like java.util.List in which the behavior of toString and equals are precisely defined. The motivation becomes clearer, however, when some broader design decisions are understood:

  • First, methods inherited from a superclass are allowed to override methods inherited from superinterfaces (§8.4.8.1). So, every implementing class would automatically override an interface's toString default. This is longstanding behavior in the Java programming language. It is not something we wish to change with the design of default methods, because that would conflict with the goal of allowing interfaces to unobtrusively evolve, only providing default behavior when a class doesn't already have it through the class hierarchy.
  • Second, interfaces do not inherit from Object, but rather implicitly declare many of the same methods as Object (§9.2). So, there is no common ancestor for the toString declared in Object and the toString declared in an interface. At best, if both were candidates for inheritance by a class, they would conflict. Working around this problem would require awkward commingling of the class and interface inheritance trees.
  • Third, use cases for declaring Object methods in interfaces typically assume a linear interface hierarchy; the feature does not generalize very well to multiple inheritance scenarios.
  • Fourth, the Object methods are so fundamental that it seems dangerous to allow an arbitrary superinterface to silently add a default method that changes their behavior.

toString() is a method of the Object class, and therefore cannot have a default implementation in any interface.

Why Java 8 compilation fails when Default Method is overridden by subclass's Static Method?

Father is not a problem here since static methods from interfaces are not inherited (for instance List.of(..) can't be invoked via ArrayList.of(..)) so there is no overriding/hiding which also means no collisions.

Because of that we can safely write

interface Father {
static void method() { }
}
interface Child extends Father {
static void method() { }
}

Problem is default void method() { } method from Mother interface which is inherited to Child which means that after

interface Child extends Father, Mother { 
static void method() { }
}

you would end up with interface which would have two method() versions: static and non-static (default)

interface Child extends Father, Mother { 
static void method() { }
default void method() { } //inherited from Mother
}

But why is that a problem?

Imagine you want to add another method to Child interface which will call method()

interface Child extends Father, Mother { 
static void method() { }
default void method() { } //inherited from Mother
default void demo(){
method(); //which code block should be executed?
}
}

Should it execute code from static method() or from default method()? Compiler wouldn't be able to decide.

Although this situation could be solved by using

  • Child.method() for static method,
  • this.method() for default method (yes, it wound't be ambiguous because static method wouldn't be inherited by class of which this would be instance),

point is to prevent such problems in the first place. That is why we are required to not have static and non-static methods with same signature (name+parameterTypes) in one place (defined or inherited).

Does Java have plan that default method (java8) Substitute for Abstract Class?

Default methods can't substitute abstract classes, as abstract classes can (and often do) have fields. Interfaces can only contain behaviour and not state, which is unlikely to change in the future as multiple inheritance of state in Java is seen (rightly or wrongly) as evil.

They can also have final methods, which is another thing you can't mimic with default methods.

If anything, interfaces with default methods resemble traits rather than abstract classes, but the match isn't perfect. Using interfaces as traits is something that has to be done very carefully and knowing the limitations they come with. (Such as any implementing class can override a default method, potentially ruining the trait.)

More on this here.



Related Topics



Leave a reply



Submit