4 Inheritance
A practical guide to modeling inheritance hierarchies, reusing and overriding behavior, applying polymorphism, and choosing composition when it produces a safer design.
The Class Relationship
connects a general class with one or more specialized classes. The existing class is the , also called a superclass or parent class. The new class is the , also called a subclass or child class.
A receives accessible members of its , then may add new fields and methods or change inherited behavior. In a typical example, Vehicle can provide a speed field and a stop() method, while Bicycle adds a gear field and a changeGear() method. The intended relationship is that a bicycle is a vehicle.
is transitive: if Bicycle derives from Vehicle and MountainBike derives from Bicycle, then MountainBike also has the inherited behavior of Vehicle. Constructors are generally not inherited; a derived-class constructor initializes its own state and may invoke a base-class constructor.
Takeaway: Use to express a meaningful specialization in which the derived type is genuinely a form of the base type.
Specialization and Reuse
supports specialization by placing stable, shared behavior in the general class and distinctive behavior in specialized classes. For example, Employee might define a name and general employee behavior, while Manager adds an operation such as approving a budget.
This arrangement can reduce duplication because common fields and methods are implemented once. Code that needs only general employee behavior can use an Employee reference, while code that requires manager-specific behavior can use Manager.
Reuse by itself is not enough to justify . The base and derived classes become closely dependent: a change to the can affect every , including classes maintained elsewhere. The relationship and behavior must therefore be conceptually correct, not merely convenient.
Takeaway: Put common behavior in a only when it meaningfully applies to every intended derived type.
Changing Inherited Behavior
lets a provide behavior appropriate to its specific kind of object. If Animal defines speak(), then Dog can override speak() to produce a bark. An annotation or language keyword such as @Override or override helps detect accidental signature mismatches.
An override may replace the base behavior completely or extend it. In Java, a derived implementation can call super.speak() and then perform additional work, such as reporting that the dog wags its tail.
is different from overloading. changes inherited behavior in a . Overloading creates methods with the same name but different parameter lists. It is also different from hiding a static method or field, where member selection can depend on the variable's declared type rather than the object's runtime type.
A sound override preserves the expectations established by the . If code written for the expects a certain result or guarantee, a derived implementation should not violate that expectation.
Takeaway: Override for genuine behavioral variation, and preserve the base-class contract when doing so.
in Practice
allows a derived object to be used wherever its base type is expected. For example, variables declared as Animal may refer to a Dog or a Cat. When speak() is called, method dispatch uses the object's runtime type to select the appropriate override.
A method can therefore process a collection of Animal objects without knowing the exact of each object. This keeps client code focused on the stable base-class contract instead of filling the code with checks such as if (animal instanceof Dog).
A may also declare abstract methods that derived classes must implement. Abstract methods make the shared contract explicit while leaving the specialized behavior to each .
Substitutability is the design requirement that code written for the continues to work correctly when it receives a derived object. The can be more specific, but it must still honor the promises made by the general type.
Takeaway: is most valuable when callers can rely on a common contract rather than identify every concrete subclass.
Designing a Sound Hierarchy
A useful hierarchy begins with a stable concept that applies to all of its derived types. Every Dog should satisfy the behavioral expectations of Animal, not merely share a few method names. The should expose a clear contract and avoid forcing subclasses to depend on fragile implementation details.
is often a poor way to represent labels or values that change between objects. Different car models usually do not need separate subclasses if the distinction can be represented by values such as make and model. A is more appropriate when it adds meaningful behavior or functionality to the base concept.
Before creating a subclass, ask:
Is the proposed derived type genuinely a kind of the proposed base type?
Does the represent stable behavior shared by all derived types?
Can derived objects honor the base-class contract?
Is the variation behavioral rather than merely a different data value?
Would a change to the create an unacceptable dependency?
Takeaway: A good hierarchy models meaningful behavioral specialization, not every possible category or data variation.
Limits and Alternatives
A contains many levels of ancestor and descendant classes. As the hierarchy grows, a developer may need to inspect several ancestors to understand one method or field. Changes near the top can alter behavior far below, and multiple overrides can make it unclear which implementation runs or whether an essential base operation was omitted.
Deep hierarchies can also complicate constructor order, initialization assumptions, testing, and reuse. A class may become tightly coupled to one branch even when the desired capability would be useful in unrelated classes. For extensible libraries, classes should be deliberately designed and documented for , or extension should be restricted when safe cannot be guaranteed.
is often the practical alternative. A Car can contain an Engine and implement start() by delegating to the engine. The car uses an engine, but it is not a specialized form of engine. Composed parts can often be replaced, tested, and combined more independently.
The choice is not absolute. Prefer for genuine specialization and polymorphic substitution. Prefer when the goal is to assemble capabilities, share replaceable behavior, or keep dependencies flexible.
Takeaway: Choose for a true is-a relationship and for a uses-a or has-a relationship whose parts may change independently.