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

5

164

Default methods are a nice new tool in our Java toolbox. However, I tried to write an interface that defines a default version of the toString method. Java tells me that this is forbidden, since methods declared in java.lang.Object may not be defaulted. Why is this the case?

I know that there is the "base class always wins" rule, so by default (pun ;), any default implementation of an Object method would be overwritten by the method from Object anyway. However, I see no reason why there shouldn't be an exception for methods from Object in the spec. Especially for toString it might be very useful to have a default implementation.

So, what is the reason why Java designers decided to not allow default methods overriding methods from Object?

Arlin answered 3/6, 2014 at 13:50 Comment(1)
I feel so good about myself now, upvoting this for 100 times and thus a gold badge. good question!Bonedry
A
215

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.

Anthropography answered 3/6, 2014 at 22:53 Comment(13)
I am glad you took the time to explain this, and I appreciate all of the factors that were considered. I agree that this would be dangerous for hashCode and equals, but I think it would be very useful for toString. For example, some Displayable interface might define a String display() method, and it would save a ton of boilerplate to be able to define default String toString() { return display(); } in Displayable, instead of requiring every single Displayable to implement toString() or extend a DisplayableToString base class.Josh
@Josh You're correct that allowing inheriting toString() would not be dangerous in the same way as it would be for equals() and hashCode(). On the other hand, now the feature would be even more irregular -- and you'd still incur all the same additional complexity about inheritance rules, for the sake of this one method...seems better to draw the line cleanly where we did.Anthropography
@Arlin If toString() is based on only the interface methods, you could simply add something like default String toStringImpl() to the interface, and override toString() in each subclass to call the interface implementation - a bit ugly, but works, and better than nothing. :) Another way to do it is to make something like Objects.hash(), Arrays.deepEquals() and Arrays.deepToString(). +1'd for @BrianGoetz's answer!Enervated
The default behaviour of a lambda's toString() is really obnoxious. I know the lambda factory is designed to be very simple and fast, but spitting out a derrived classname really isn't helpful. Having a default toString() override in a functional interface would allow us to --at least-- do something like spit out the signature of the function and the parent class of the implementer. Even better, if we could bring some recursive toString strategies to bear we could walk through the closure to get a really good description of the lambda, and thus improve the lambda learning curve drastically.Partook
Any change to toString in any class, subclass, instance member could have that effect on implementing classes or users of a class. Furthermore, any change to any of the default methods would also likely affect all the implementing classes. So what's so special with toString, hashCode when it comes to someone altering the behaviour of an interface? If a class extends another class they could alter than too. Or if they are using the delegation pattern. Someone using Java 8 interfaces will have to do so by upgrading. A warning/error that can be suppressed on the subclass could've been provided.Encounter
Surely the last point applies exactly as much is Bar is a class rather than an interface.Headliner
If you use log4j2, one workaround for the toString problem is to let the interface extend log4j.util.StringBuilderFormattable and implement formatTo as a default methodSpermatium
IMO, Object is not really a class, but a mix of the basic implementation stuff, and fallback methods. Maybe these fallback methods (like toString()) should be overridable by interfaces. My biggest "personal pain" is the impossibility of this type of templating: public default toString() { return toString(Locale.getDefault()); }.Genarogendarme
This all goes to show what a mess Java interfaces are. They were a terrible idea from the start. The "problem" they were supposed to solve is that multiple inheritance can cause ambiguity in the event that there is diamond inheritance. In practice it's actually just fine (it's just occasionally necessary to qualify otherwise ambiguous access - just as we do when there are multiple default implementations of interface methods!). Now Java inches towards multiple inheritance by another name with successive "enhancements". But still interfaces are hobbled compared to abstract classes.Duplication
@Duplication It is a common mistake to lump all kinds of "multiple inheritance" in the same bucket, and naively tar them all with the brush of "but it worked so badly in C++." Java always had multiple inheritance of types, which was not problematic. Default methods add multiple inheritance of behavior, which is similarly non-problematic; while you can construct "diamonds", they are not a problem as the compiler detects them and has you resolve them explicitly. It is multiple inheritance of state that is actually problematic, but neither interfaces nor abstract classes let you do this.Anthropography
@BrianGoetz I perhaps didn't make myself clear... I'm fine with multiple inheritance (including that of state) and I wasn't trying to tar it with any brush! I just wish Java would let me do it properly. Why I think interfaces are a mess, and were a bad idea to start with, is that they are getting closer and closer to C++-style multiple inheritance, but in a completely cock-a-maimy way which forces you to into bad practices. [Long comment continues...]Duplication
So, for example, I can define default methods, but I can't make them final (as they generally should be). I can let the default methods call abstract (interface) methods which subclasses have to implement (fundamental principal of OO design). But I have to make those abstract methods public, not protected as they almost always should be. Developers can declare static data fields without the static keyword (though I never would!). How arbitrary and confusing is that? They're just a mess.Duplication
Re default implementations of equals() and hashCode() vs toString(), I think it's the other way round. There are many ways you might want to implement toString(), but there's really only one implementation of hashCode() and of equals() which anyone ever wants (now written as boilerplate every time). A "HasIdentity" interface could implement hashCode() and equals() using garden-variety comparison/hashing on a list of "identity" properties supplied by a method getIdentityProperties() which returns a List<Supplier<Object>>. And you never have to write an equals() or hashCode() method ever again.Duplication
P
34

It is forbidden to define default methods in interfaces for methods in java.lang.Object, since the default methods would never be "reachable".

Default interface methods can be overwritten in classes implementing the interface and the class implementation of the method has a higher precedence than the interface implementation, even if the method is implemented in a superclass. Since all classes inherit from java.lang.Object, the methods in java.lang.Object would have precedence over the default method in the interface and be invoked instead.

Brian Goetz from Oracle provides a few more details on the design decision in this mailing list post.

Propylene answered 3/6, 2014 at 14:4 Comment(0)
L
5

To give a very pedantic answer, it is only forbidden to define a default method for a public method from java.lang.Object. There are 11 methods to consider, which can be categorized in three ways to answer this question.

  1. Six of the Object methods cannot have default methods because they are final and cannot be overridden at all: getClass(), notify(), notifyAll(), wait(), wait(long), and wait(long, int).
  2. Three of the Object methods cannot have default methods for the reasons given above by Brian Goetz: equals(Object), hashCode(), and toString().
  3. Two of the Object methods can have default methods, though the value of such defaults is questionable at best: clone() and finalize().

    public class Main {
        public static void main(String... args) {
            new FOO().clone();
            new FOO().finalize();
        }
    
        interface ClonerFinalizer {
            default Object clone() {System.out.println("default clone"); return this;}
            default void finalize() {System.out.println("default finalize");}
        }
    
        static class FOO implements ClonerFinalizer {
            @Override
            public Object clone() {
                return ClonerFinalizer.super.clone();
            }
            @Override
            public void finalize() {
                ClonerFinalizer.super.finalize();
            }
        }
    }
    
Limonene answered 2/9, 2017 at 1:25 Comment(2)
.Whats the point? You still didn't answer the WHY part - "So, what is the reason why Java designers decided to not allow default methods overriding methods from Object?"Recapitulation
@Recapitulation His point is, as clearly stated, to be very pedantic. The answer adds some bits of info to the excellent accepted one, which already covered the WHY part.Naught
J
3

I do not see into the head of Java language authors, so we may only guess. But I see many reasons and agree with them absolutely in this issue.

The main reason for introducing default methods is to be able to add new methods to interfaces without breaking the backward compatibility of older implementations. The default methods may also be used to provide "convenience" methods without the necessity to define them in each of the implementing classes.

None of these applies to toString and other methods of Object. Simply put, default methods were designed to provide the default behavior where there is no other definition. Not to provide implementations that will "compete" with other existing implementations.

The "base class always wins" rule has its solid reasons, too. It is supposed that classes define real implementations, while interfaces define default implementations, which are somewhat weaker.

Also, introducing ANY exceptions to general rules cause unnecessary complexity and raise other questions. Object is (more or less) a class as any other, so why should it have different behaviour?

All and all, the solution you propose would probably bring more cons than pros.

Junk answered 3/6, 2014 at 14:20 Comment(1)
I didn't notice the second paragraph of gexicide's answer when posting mine. It contains a link that explains the issue in more detail.Junk
Y
3

The reasoning is very simple, it is because Object is the base class for all the Java classes. So even if we have Object's method defined as default method in some interface, it will be useless because Object's method will always be used. That is why to avoid confusion, we cannot have default methods that are overriding Object class methods.

Yarak answered 15/4, 2016 at 8:19 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.