Concepts / Declaring Interfaces in Java

Declaring Interfaces in Java

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

  • Programming

A Default Method Surprise

Interfaces in Java 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. The question this article keeps returning to is simple to ask and easy to get wrong: when you call a method on an object, whose code actually runs?

What do you think happens?

The variable a has the type Greeter, and its object is a Formal. What do you think b.greet() prints? Make a guess before reading on.

  • Hello, Dr. Lovelace!

  • Good day, Dr. Lovelace.

  • Good day, Ada.

Reveal answer
Answer:

Good day, Dr. Lovelace.

Formal supplies its own greet() body, so that body runs. The declared type Greeter does not decide which greet() body is used; the class of the object does.

Where a Default Body Lives

Definition

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

The usual reason to give an interface a body 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. The implementation therefore lives in the interface, and the class only has to supply what the interface leaves open.

Consider a small interface that mixes the two kinds of method. The method readChar() has no body, so it is abstract and every implementing class must write it. The method readLine() has a body, so implementing classes inherit it. The interface is doing two jobs at once: it sets a requirement with readChar(), and it supplies a shared piece of behavior, readLine(), that is built on that requirement.

java
Output
***
Notes on the code
  • readChar() has no body, so it is abstract and Stars must write it
  • readLine() has a body, so Stars inherits it without writing it again
  • Stars never defines readLine(), yet calling it on a Stars object works

Tracing Which Body Runs

When an object receives a call to a default method, the first thing to establish is which class the object belongs to. Java looks at the object's own class to decide what runs. If that class does not override the method, the interface's default body runs. If the class does override it, the class's own body runs and the interface's default body is never reached for that object.

java
Output
Hello, Ada!
Good day, Dr. Lovelace.
Notes on the code
  • Plain uses the default greet() exactly as the interface wrote it
  • Formal overrides greet() with its own body
  • Both variables are declared as Greeter, but each object runs its own class's greet()
maincallerPlain objectclass with no greet()Greeter.greet()default bodyinherited by Plainname() in Plainreturns Adaa.greet()no override, inherited body runsname()AdaHello, Ada!
For a Plain object, which code runs when greet() is called, and where does the call to name() get answered?
Worked example

Tracing b.greet() on a Formal object

Given the Greeter, Plain, and Formal classes above, what does System.out.println(b.greet()) print when b is a Formal object?

  1. 1
    Identify the object

    The variable b is declared as Greeter, but the object it refers to is a Formal.

  2. 2
    Check for an override

    Formal declares its own greet(), so the interface's default body is not the one that runs.

  3. 3
    Run the class's body

    Formal's greet() evaluates "Good day, " + name() + ".".

  4. 4
    Answer the abstract call

    name() is answered by Formal, which returns Dr. Lovelace.

  5. 5
    Assemble the output

    The string becomes Good day, Dr. Lovelace.

Result

Good day, Dr. Lovelace.

How a Default Body Reaches the Class's Code

The default body of readLine() calls readChar(), but the interface does not know how readChar() works. Only the class that eventually creates an object knows that. So the call inside the default method is a request that gets answered by whichever implementing class the object belongs to. This is what it means for the reference to the abstract method to be polymorphic: one line of code in the interface can behave differently depending on the object it runs against.

main()calls readLine()CharSource.readLine()default body in theinterfaceStars.readChar()supplied by theimplementing classcalls readLine()calls readChar()
When readLine() runs on a Stars object, which code is active at each step, and which class answers the readChar() call?

Read the diagram as a stack of active calls. The interface owns readLine(), and inside it readLine() reaches for readChar(). The Stars class is the one that fills in readChar(). Nothing in the interface's default body names Stars, which is why the same body would also work for any other class that supplies a readChar(). When the inner call returns, control goes back into readLine(), which finishes building its result.

Overriding Versus Inheriting

A class that does not override a default method still gets that behavior, and a class that does override it gets its own behavior instead. The before-and-after pair below changes only one thing: whether Formal provides its own greet(). The output changes because the code that runs changes.

Before
public class Formal implements Greeter {
    public String name() {
        return "Dr. Lovelace";
    }
}

Greeter b = new Formal();
System.out.println(b.greet());

Formal inherits greet() from Greeter and prints Hello, Dr. Lovelace!

After
public class Formal implements Greeter {
    public String name() {
        return "Dr. Lovelace";
    }
    public String greet() {
        return "Good day, " + name() + ".";
    }
}

Greeter b = new Formal();
System.out.println(b.greet());

Formal overrides greet() and prints Good day, Dr. Lovelace.

SituationWhich greet() body runsWhere name() is answered
Plain object, no overrideThe interface's default greet()Plain.name()
Formal object, overrides greet()Formal's own greet()Formal.name()
Greeter-typed variable holding a FormalFormal's own greet()Formal.name()

The declared type of the variable does not change which body runs; the object's class does.

Mistakes When Tracing Output

When a printed result surprises you, the useful question is not whether the method is wrong but which copy of the method ran. The mistakes below are the ones that most often send a tracer down the wrong path.

  • Common mistake

    Reading the variable's declared type to decide which body runs

    What it looks like

    Greeter b = new Formal(); then assuming b.greet() must use the Greeter body because b is declared as Greeter.

    Why it fails

    Method selection on an object depends on the object's class, so Formal's override is the one that runs.

    Do this instead

    Find the object first, then check the object's class for an override.

  • Common mistake

    Assuming an overriding class's method runs every time

    What it looks like

    Expecting Formal's greet() to appear for a Plain object.

    Why it fails

    Plain does not override greet(), so the interface's default body is used for Plain objects.

    Do this instead

    Check each object's class separately; one class's override does not affect other classes.

  • Common mistake

    Searching only the class for the code that runs

    What it looks like

    Looking for the text Hello in Plain's source and not finding it, then concluding the output is impossible.

    Why it fails

    The default body is stored in the interface, not in each implementing class.

    Do this instead

    Follow the call to the interface when the class does not define the method.

  • Common mistake

    Forgetting that the abstract call is answered by the object's class

    What it looks like

    Tracing readLine() on Stars and expecting the interface itself to produce the characters.

    Why it fails

    The interface calls readChar(), and Stars supplies the answer.

    Do this instead

    At every call inside a default body, ask which class answers that call.

  1. 1
    Locate the object that produced the line of output.
  2. 2
    Check whether that object's class overrides the method.
  3. 3
    If it does not, follow the call to the interface's default body.
  4. 4
    Find the abstract call inside that body and identify the class that answers it.
  5. 5
    Mark the exact step where your expected value and the actual value first differ.

Practicing the Trace

MEDIUM

Add a third class named Casual that implements Greeter and overrides name() to return "Sam" but does not override greet(). Then, without running the code, predict the output of System.out.println(c.greet()) when c is a Casual object. Next, add a fourth class named Silent that implements Greeter and overrides greet() to return an empty string, and predict what Silent's greet() returns when it is called through a Greeter variable. Compare each prediction with the trace rules: find the object, check for an override, and identify which name() answered.

Hints
  • Casual does not override greet(), so the interface's default body runs for it.
  • The default body calls name(), and the object's class answers that call with Sam.
  • Silent overrides greet() and does not call name(), so its own body is the only code involved.

Key Takeaways

  • A default method is declared in an interface with a body, and implementing classes inherit that body without rewriting it.
  • A default method can call abstract methods, and each call is answered by the object's own class, which makes the default body behave differently for different objects.
  • When you call a default method on an object, check the object's class first: an override runs its own body, and a class without an override runs the interface's default body.
  • To trace unexpected output, locate the object, check for an override, follow the call into the default body, and identify which class answered each abstract call.