Search code examples
javainterfaceabstract-class

When to use interface vs abstract class after Java 8


I know this question has been asked many times and we have articles all over the internet but I still cannot fully understand, when should I use interface or abstract class since I am using Java 15.

Most of the articles talk about the differences and use cases before Java 8 which makes sense but not when you can basically provide body for your methods in interface.

The only thing that made sense to me is non-public and non-final restrictions.

I would really appreciate if somebody can point out 1-2 examples of the scenarios where I need to choose between interface and abstract class in Java 15. Also, would be great if it can be in terms of real life projects rather than Animal or shape class examples.

Thanks !!


Solution

  • default methods on interface

    Apparently you are referring to the feature of “default methods” implementing behavior in an interface.

    You should understand that the feature was added as a way around this dilemma: How to retroactively add features leveraging streams and lambda on existing interfaces without breaking existing classes that implement those interfaces?

    Many new methods were added to those interfaces such as in the Java Collections Framework. Adding methods to an existing interface would automatically break all classes implementing the interface that are lacking the newly-required methods. Being able to provide a fallback, to give an implementation where one is now required but not yet existing, would resolve the dilemma. Thus « default methods » were born.

    To quote from the Oracle tutorial linked above:

    Default methods enable you to add new functionality to the interfaces of your libraries and ensure binary compatibility with code written for older versions of those interfaces.

    To quote this Answer by Brian Goetz, Java Language Architect at Oracle:

    The proximate reason for adding default methods to interfaces was to support interface evolution

    So this feature of adding default behavior to an interface was not meant to be a new mainstream feature in and of itself. The intent of default was not to replace abstract.

    Indeed, some experienced Java experts have recommended against making a habit of writing default methods on an interface. They recommend pretty much ignoring that feature of the Java language. Continue to think of interfaces as simply defining a contract, and abstract classes as providing partial implementations meant to be completed in a subclass.

    You are certainly free to write your own default methods on your interfaces. And certainly you should do so if you are in a similar situation of having published an interface that others may have implemented, and now you want to add methods. But unnecessarily adding default methods is likely to confuse other programmers who expect partial implementations on an abstract class rather than an interface.

    Four situations calling for default method on an interface

    In that same post linked above, Brian Goetz suggests three other cases beyond interface evolution where default methods on an interface may be appropriate. Here is a quick mention; see his post for details.

    • Optional methods - The default method throws an UnsupportedOperationException because we expect implementations of this interface to more often not want to implement this method.
    • Convenience methods
    • Combinators

    Start with interface, move to abstract class for shared code

    As for choosing between an interface and abstract class:

    • Generally start with an interface. Or several interfaces if you want various implementing classes to mix various contracts (see mixin).
      • Think twice before adding a default method. Consider if your situation meets one of the four cases recommended by Brian Goetz as discussed above.
    • If you come to realize that you have duplicated code across multiple classes, then consider centralizing that shared code to an abstract class to be used across subclasses.
      • Alternatively, use composition rather than inheritance (discussed below).

    For example, you might have a class for domestic ShippingLabelUS as well as ShippingLabelCanada and ShippingLabelOverseas. All three need to convert between imperial pounds and metric kilograms. You find yourself copying that code between the classes. At this point you might consider having all three classes extend from abstract class ShippingLabel where a single copy of the weight conversion methods live.

    While designing your API keep in mind that Java, like most OOP languages, has single-inheritance. So your subclasses are limited to extending only one class. To be a bit more specific about the single-versus-multiple inheritance, I will quote Brian Goetz from this PDF of a slide deck:

    [regarding default methods on an interface]

    Wait,is this multiple inheritance in Java?

    • Java always had multiple inheritance of types

    • This adds multiple inheritance of behavior

    • But not of state, where most of the trouble comes from

    Composition over inheritance

    An alternative to using an abstract class for shared behavior is creating a separate class for specific behavior, then adding an object of that separate class to be kept as a member field on the larger class. Wise programmers often share this pearl of wisdom: Prefer composition over inheritance.

    Regarding the shipping label example above, you could create a WeightConverter class, an object of which would be a member of each of the three label classes. In this arrangement, no need for the abstract class.