Chapter 5: Inheritance, Abstraction, and Interfaces
This chapter teaches how Java models related types and shared behavior. You will learn inheritance, abstract classes, interfaces, overriding, polymorphism, and composition tradeoffs through store examples, but the core goal is to build strong theory for object-oriented interviews and real code design.
Why Chapter 5 moves from single classes to related types
By Chapter 4, the learner can model single classes and groups of objects. The next step is expressing relationships between types. Some objects share behavior. Some need a common contract. Some need a base abstraction. Java gives several tools for this: inheritance, abstract classes, interfaces, and composition.
The project context may talk about different product types or payment strategies, but the underlying skill is broader. Interviews use these topics to test whether you understand when reuse is healthy, when a contract should be separated from an implementation, and how runtime polymorphism really works.
- Inheritance expresses an is-a relationship between classes.
- Interfaces define behavior contracts without committing to one implementation.
- Composition is often a better reuse tool when inheritance would be too rigid.
interface DiscountPolicy {
BigDecimal apply(BigDecimal subtotal);
}
Inheritance basics and what subclassing really means
Inheritance lets one class extend another. The subclass inherits accessible members from the parent class and can add or override behavior. This can reduce duplication when the relationship is genuine, but it also couples the child to the parent’s design.
A weak inheritance hierarchy often begins with trying to reuse code instead of modeling a true is-a relationship. That is why strong answers discuss both the benefit and the design cost of subclassing rather than treating inheritance as automatically good.
- Use inheritance when the subtype truly is a specialized form of the parent type.
- Subclasses inherit behavior but also inherit constraints.
- Inheritance is a design commitment, not just a shortcut for reuse.
class Product {
private final String name;
Product(String name) {
this.name = name;
}
}
class DigitalProduct extends Product {
DigitalProduct(String name) {
super(name);
}
}
Method overriding and runtime polymorphism
Overriding happens when a subclass provides its own implementation of an inherited method with the same signature. The important part is not the syntax alone. It is that Java decides at runtime which implementation to execute based on the actual object type.
This is dynamic dispatch, and it is one of the main reasons polymorphism works. Interview questions often use short code examples here because they reveal whether a candidate understands the difference between the reference type and the runtime object type.
- Overriding changes inherited behavior in a subtype.
- Dynamic dispatch picks the implementation using the runtime type.
- Reference type and object type are related but not identical ideas.
class Product {
String label() {
return "Generic product";
}
}
class DigitalProduct extends Product {
@Override
String label() {
return "Digital product";
}
}
Product item = new DigitalProduct();
System.out.println(item.label()); // Digital product
Abstract classes versus interfaces
Abstract classes are useful when related types should share state or some common implementation. Interfaces are useful when you mainly want to express a contract that multiple unrelated classes can implement. Modern Java interfaces can also include default and static methods, but they still do not replace the role of a state-bearing base class.
The key interview skill is not memorizing a table of differences. It is explaining why one choice fits a design better than the other. If the design needs shared fields and partial implementation, an abstract class may fit. If the design needs a flexible capability contract, an interface is often better.
- Abstract classes support shared state and shared implementation.
- Interfaces express capabilities and contracts across unrelated types.
- The right choice depends on the design goal, not on a memorized rule alone.
abstract class PaymentMethod {
abstract boolean authorize(BigDecimal amount);
}
interface Refundable {
void refund(BigDecimal amount);
}
Interfaces, composition, and substitutable design
Interfaces make code easier to substitute and test because callers can depend on behavior instead of a concrete class. This fits Java backend work especially well, where services often depend on a contract rather than one implementation. Composition takes this further by wiring objects together instead of forcing one class to inherit everything from another.
A strong design answer often says: use inheritance for a real subtype relationship, use interfaces for behavior contracts, and use composition when you want flexibility without a rigid hierarchy. That is much stronger than saying inheritance is object-oriented and therefore always preferred.
- Interfaces support loose coupling and easier substitution.
- Composition assembles behavior from parts instead of forcing hierarchy.
- Flexible design usually depends more on contracts than on deep inheritance trees.
class CheckoutService {
private final DiscountPolicy discountPolicy;
CheckoutService(DiscountPolicy discountPolicy) {
this.discountPolicy = discountPolicy;
}
}
Default methods, multiple inheritance rules, and design judgment
Java interfaces can declare default methods, which means interfaces are no longer pure method signatures only. This is useful for evolving APIs, but it also introduces conflict rules when a class inherits competing defaults. Java resolves this with clear precedence rules, including the rule that class methods win over interface defaults.
This topic matters because it tests precision. It also leads to a bigger design lesson: even when Java allows a feature, that does not mean every design should use it heavily. Good engineers use default methods to improve contracts carefully, not to recreate messy inheritance trees through interfaces.
- Default methods help evolve interfaces without breaking every implementation.
- Class methods take precedence over interface default methods.
- Design judgment matters more than showing off every available language feature.
interface Auditable {
default String auditLabel() {
return "auditable";
}
}
class Product implements Auditable {}
Chapter takeaway
Inheritance and interfaces are not just syntax features. They are design tools for expressing shared contracts, substitutable behavior, and code that stays flexible as a system grows.