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.
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.
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.
Hello, Ada!
Beep R2.- 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?
Reveal 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.
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.
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?
- 1Identify the object
The variable r was assigned new
Robot(), so the object belongs to class Robot even though the variable is typed as Greeter. - 2Check 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. - 3Run the override
Robot's
greet()returns the text Beep followed by the result ofname()and a period. The call toname()is answered by Robot, which returns R2. - 4Assemble the string
The concatenation gives Beep R2. which is the line printed.
Beep R2.
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.
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 likeReading the Robot object's greet() call and reasoning only from the Greeter interface.
Why it failsAn implementing class may override a default method, so the object's class must be checked first.
Do this insteadIdentify 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 likeExpecting greet() in Greeter to print Ada specifically because Person was the first class written.
Why it failsThe 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 insteadFollow 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 likeSearching Person's source for the text Hello, and concluding that the greeting is missing.
Why it failsThe shared body is stored once in the interface and reached by inheritance, not copied into each class.
Do this insteadLook 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
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'sname()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.