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.
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.
Reveal 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
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.
***readChar()has no body, so it is abstract and Stars must write itreadLine()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.
Hello, Ada!
Good day, Dr. Lovelace.- 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()
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?
- 1Identify the object
The variable b is declared as Greeter, but the object it refers to is a Formal.
- 2Check for an override
Formal declares its own
greet(), so the interface's default body is not the one that runs. - 3Run the class's body
Formal's
greet()evaluates "Good day, " +name()+ ".". - 4Answer the abstract call
name()is answered by Formal, which returns Dr. Lovelace. - 5Assemble the output
The string becomes Good day, Dr. Lovelace.
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.
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.
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!
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.
| Situation | Which greet() body runs | Where name() is answered |
|---|---|---|
| Plain object, no override | The interface's default greet() | Plain.name() |
| Formal object, overrides greet() | Formal's own greet() | Formal.name() |
| Greeter-typed variable holding a Formal | Formal'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 likeGreeter b = new Formal(); then assuming b.greet() must use the Greeter body because b is declared as Greeter.
Why it failsMethod selection on an object depends on the object's class, so Formal's override is the one that runs.
Do this insteadFind 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 likeExpecting Formal's greet() to appear for a Plain object.
Why it failsPlain does not override
greet(), so the interface's default body is used for Plain objects.Do this insteadCheck 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 likeLooking for the text Hello in Plain's source and not finding it, then concluding the output is impossible.
Why it failsThe default body is stored in the interface, not in each implementing class.
Do this insteadFollow 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 likeTracing readLine() on Stars and expecting the interface itself to produce the characters.
Why it failsThe interface calls
readChar(), and Stars supplies the answer.Do this insteadAt every call inside a default body, ask which class answers that call.
- 1Locate the object that produced the line of output.
- 2Check whether that object's class overrides the method.
- 3If it does not, follow the call to the interface's default body.
- 4Find the abstract call inside that body and identify the class that answers it.
- 5Mark the exact step where your expected value and the actual value first differ.
Practicing the Trace
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 callname(), 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.