Concepts / Abstract Classes Versus Interfaces

Abstract Classes Versus Interfaces

A default method is a method declared in an interface with a body, so implementing classes inherit a shared implementation without rewriting it.

  • Programming

Why an Interface Might Hold a Body

Java interfaces usually describe what an object can do without saying how it does it. A class that promises to satisfy an interface must supply the bodies of the methods the interface lists. Default methods change that arrangement a little. An interface can now carry a method body of its own, and every class that implements the interface receives that body without writing it again.

Why would an interface want a body at all? The usual reason is that some behavior makes sense for nearly every class that implements the interface. Rather than forcing each implementing class to copy the same code, the interface holds that shared behavior once. An implementing class gets the behavior for free, and it can still provide its own version if it needs something different.

Definition

A default method is a method declared in an interface with a body, so implementing classes inherit a shared implementation without rewriting it.

Predicting the Greeting Program

Before you learn the rules, read the program below and decide what it prints. Two classes implement the same interface. One uses the default greeting as it is, and the other overrides it. Write down your guess for each printed line, and note which method body you think produced it. You will check your guess against the explanation that follows.

java
Output
Hello, Ada!
Beep R2.
Notes on the code
  • Greeter declares name() without a body, so every implementing class must supply it.
  • Greeter gives greet() a body, so implementing classes inherit it.
  • Person does not write greet(), so it uses the interface's default body.
  • Robot writes its own greet(), which replaces the default for Robot objects.

What do you think happens?

What does the program print, line by line?

  • Hello, Ada! and then Hello, R2!

  • Hello, Ada! and then Beep R2.

  • Beep Ada. and then Beep R2.

  • Beep Ada. and then Hello, R2!

Reveal answer
Answer:

Hello, Ada! and then Beep R2.

The Person object has no greet() of its own, so the call runs the interface's default body, which builds the greeting from name() and produces Hello, Ada!. The Robot object has its own greet(), so that body runs instead and produces Beep R2.

Where the Default Body Lives

The interface holds the default body once. Implementing classes do not get a copy of it pasted into their own code. Instead, an object of an implementing class can reach the interface's body when a call needs it. This is why a default method is described as inherited behavior: the implementation has one home, and every implementing class can use it.

The interface is therefore doing two jobs at once. It sets a requirement, because abstract methods have no body and every implementing class must write them. It also supplies a shared piece of behavior, because a default method is built on top of that requirement. In the greeting program, name() is the requirement and greet() is the shared behavior.

The source illustrates this with an interface that has two methods. readChar() has no body in the interface, so it is abstract and every implementing class must write it. readLine() has a body, so implementing classes inherit it. The default body of readLine() calls readChar(), but the interface does not know how readChar() works. Only the class that creates the object knows that. The Stars class is the one that fills in readChar(), and nothing in the interface's default body names Stars.

This is what the source means when it says the call to the abstract method is polymorphic. One line of code in the interface, such as the call to name() inside greet(), can behave differently depending on the object it runs against. The same default body would work for any other class that supplies a readChar(), because the body only asks for the detail and never names the class that answers.

main()Person objectclass Persongreet() default bodyfrom Greetername() in Personreturns Adagreet()no override, use defaultname()AdaHello, Ada!Hello, Ada!
When p.greet() runs on a Person object, which body runs first, and which class answers the call to name()?

Following the Calls Through a Default Method

To trace a call, begin with the object, not the method name. The variable's declared type is Greeter in both cases, but the object it refers to decides which greet() body runs. Check whether that object's class overrides the method. If it does, the override runs. If it does not, the interface's default body runs, and its call to name() is answered by the object's own class.

Worked example

Tracing r.greet() on a Robot object

Given the greeting program, what does System.out.println(r.greet()) print when r refers to a Robot object?

  1. 1
    Identify the object

    The variable r was assigned new Robot(), so the object belongs to class Robot even though the variable is typed as Greeter.

  2. 2
    Check for an override

    Robot writes its own greet() with the @Override annotation. That means the call goes to Robot's body and never reaches the interface's default body.

  3. 3
    Run the override

    Robot's greet() returns the text Beep followed by the result of name() and a period. The call to name() is answered by Robot, which returns R2.

  4. 4
    Assemble the string

    The concatenation gives Beep R2. which is the line printed.

Result

Beep R2.

main()greet() default bodydefined in Greetername() in Personanswers with Adacalls greet()calls name()
At the moment name() is called, which frames are on the stack, and which class's version of name() answers?

The call stack above shows the order of events. The default body is entered because Person does not override greet(). Inside that body, the call to name() reaches Person, because the object belongs to Person. The only class-specific piece is the answer to name(). The rest of the greeting comes from the interface's shared text.

Finding Where Output Diverges

When a printed line surprises you, the useful question is not whether greet() is wrong but which greet() ran. Work through the questions in a fixed order: which object produced the line, whether that object's class overrides the method, and if it does not, where the abstract call inside the default body was answered. The flowchart below follows that order.

Identify the objectDoes its classoverride greet()?Run the override bodyRobot in the programRun the interfacedefault bodyPerson in the programFind which classanswers name()yesno
Starting from an unexpected printed line, which question should you ask first, and where does the search end?

Return to the guesses you wrote before reading the explanation. For each guess, identify the body that ran, the abstract method it called, and the class that answered. If a guess was wrong, mark the point where your reasoning and the program's behavior split apart. That point is usually the place where an override was missed or where a call to name() was assumed to go to the wrong class.

Mistakes When Tracing Default Methods

  • Common mistake

    Assuming the interface's default body always runs

    What it looks like

    Reading the Robot object's greet() call and reasoning only from the Greeter interface.

    Why it fails

    An implementing class may override a default method, so the object's class must be checked first.

    Do this instead

    Identify the object, then check whether its class overrides greet(), before deciding which body ran.

  • Common mistake

    Thinking the default body names the class that answers the abstract call

    What it looks like

    Expecting greet() in Greeter to print Ada specifically because Person was the first class written.

    Why it fails

    The default body only calls name(). The interface does not know which class will answer, and the answer depends on the object at runtime.

    Do this instead

    Follow the call from the default body to the object's own class, and use that class's name() result.

  • Common mistake

    Looking for the default body inside each implementing class

    What it looks like

    Searching Person's source for the text Hello, and concluding that the greeting is missing.

    Why it fails

    The shared body is stored once in the interface and reached by inheritance, not copied into each class.

    Do this instead

    Look in the interface for the default body, and in the implementing class only for the abstract method and any override.

  • Common mistake

    Forgetting that an override replaces the default for that class only

Practice Tracing a Default Method

EASY

Suppose you add a third class, Dog, that implements Greeter. Its name() returns Rex, and it does not override greet(). Predict what Dog's greet() call prints, and state which body ran and which class answered the call to name().

Hints
  • Dog does not override greet(), so which body is the object's class going to use?
  • The default body calls name(). Which class's name() does the Dog object reach?
  • Compare your answer with the Person object, which follows the same path.

Key Takeaways

  • A default method is a method declared in an interface with a body, so implementing classes inherit a shared implementation without rewriting it.
  • A default method can call abstract methods, and those calls are answered by the object's own class, which is what makes the default implementation polymorphic.
  • An implementing class may override a default method, so tracing output means first checking which class the object belongs to.
  • When output diverges from expectation, locate the object, then whether its class overrides the method, then which abstract call supplied the detail.