Understanding Inheritance
Learn how inheritance in Java lets you create class hierarchies to reuse code and reduce redundancy. Understand the role of the extends keyword, protected access modifier, and super keyword for constructors. This lesson helps you grasp how subclasses inherit from superclasses, override methods, and build flexible, logical application designs.
When building complex applications, we often find ourselves writing the same code for different classes. If we are building a payroll system, we might define a Manager class and a Developer class. Both need names, IDs, and salaries. Copying and pasting this logic leads to errors and makes updates difficult. If we need to change how salaries are stored, we have to update it in multiple places.
Java solves this with inheritance, a mechanism that allows a new class to adopt the properties and behaviors of an existing class. This creates a logical hierarchy, reduces redundancy, and enables us to write cleaner, more maintainable code.
What is inheritance?
Inheritance is the mechanism by which a subclass inherits members from a superclass. The subclass contains the superclass part of the object, but access to inherited members is still controlled by access modifiers. This allows us to define a general class with common features and create specific classes that extend it.
We use specific terminology to describe this relationship:
Superclass (parent): The class being extended. It holds the common attributes and behaviors.
Subclass (child): The class that extends the superclass. It inherits features from the parent and can add its own specific features.
Inheritance models an “is-a” relationship. If we have a class Vehicle and a subclass Car, we can say “A Car is a Vehicle.” If this sentence makes logical sense, inheritance is likely the right design choice.
The protected modifier
Before we write code to extend classes, we must introduce a new access modifier: protected.
Previously, we used private (accessible only within the class) and public (accessible everywhere). However, inheritance creates a special “family” relationship. We often want a child class to access the parent’s internal state, but we do not want to expose that state to the entire world.
The protected modifier solves this. A member marked as protected is accessible:
Within the same package (like package-private).
By subclasses, even if they are in a different package.
Here is how protected compares to the other access modifiers:
Modifier | Access within class | Access within subclass | Access within package | Global access |
| Yes | No | No | No |
| Yes | Yes | Yes | No |
(no keyword) | Yes | Only for subclasses in the same package | Yes | No |
| Yes | Yes | Yes | Yes |
Think of protected as family secrets: shared between parents and children, but hidden from strangers.
Extending classes with extends
We establish inheritance in Java using the extends keyword. When a subclass extends a superclass, it inherits the parent’s members, but what it can access depends on visibility: public and protected are accessible, package-private is accessible only in the same package, and Private is not directly accessible.
Let’s look at a basic hierarchy where a Car inherits state from a Vehicle.
Note: In real-world code, fields are usually kept private and exposed through protected or public methods. We use a protected field here only to demonstrate how inheritance access works.
Line 3: We use
protectedforbrand. If we usedprivate,Carwould not be able to access it directly.Line 10: The
Carclass usesextends Vehicleto inherit functionality.Line 15: Inside
Car, we accessbrandas if it were defined inCaritself. This works because of theprotectedmodifier.Lines 24–27: We create an instance of
Carand callhonk(), which is inherited fromVehicle.
The super keyword and constructors
Constructors are not inherited. When we create an instance of a subclass, Java must essentially “build” the parent part of the object before it builds the child part. This ensures that any logic the parent relies on, such as initializing fields, executes first.
We use the super keyword to call a constructor in the superclass.
Implicit call: If we do not call
super()explicitly, Java automatically inserts a call to the no-argument constructor of the superclass. This works only if the superclass has an accessible no-argument constructor. If it does not, the subclass must explicitly call an existing superclass constructor.Explicit call: If the superclass does not have a no-argument constructor (e.g., it only has a constructor that takes arguments), the subclass must explicitly call
super(arguments)as the very first line of its own constructor.
Here is how we handle parameterized constructors in a hierarchy.
Line 5: The
Employeeconstructor takes aStringargument. It does not have a default no-argument constructor.Line 14: The
Managerconstructor accepts bothname(for the parent) andteamSize(for itself).Line 16: We call
super(name)to pass the name up to theEmployeeconstructor. If we omitted this line, the code would not compile.Line 17: After the parent is initialized, we initialize
Manager-specific fields.Line 28: Creating a
Managertriggers the chain:Employeeinitializes first, followed byManager.
Accessing superclass members
If a subclass declares a field with the same name, it hides the superclass field. If it declares an instance method with the same signature, it overrides the superclass method. If we need to reference the member belonging to the parent class explicitly, we use the super keyword followed by the dot operator (e.g., super.fieldName or super.methodName()).
This is distinct from this, which refers to the current instance. super specifically looks up the inheritance chain.
Line 2: The parent class
Messagehas aprintmethod.Line 8: The
Alertclass also defines aprintmethod.Line 10: Inside the child’s method,
super.print()explicitly executes the code insideMessage.Line 11: The child then executes its own additional logic.
Line 17: Calling
print()on the object triggers the child’s method, which internally leverages the parent’s behavior.
The single inheritance rule
Java follows a single inheritance model for classes. A class can extend only one direct superclass. We cannot write class C extends A, B. This rule exists to avoid the “Diamond Problem”. If classes A and B both had a method execute(), and C inherited from both, Java would not know which execute() method to run.
However, Java supports multi-level inheritance. A class can inherit from a class that is already a subclass of another. The inheritance chain is transitive: if C extends B and B extends A, then C is also an A and inherits everything from both B and A.
Here is an example of a multi-level hierarchy: Animal Mammal Dog.
Line 1:
Animalis the root superclass.Line 7:
MammalextendsAnimal, inheritingeat().Line 13:
DogextendsMammal, inheriting bothbreathe()(direct parent) andeat()(grandparent).Lines 23–25: The
dogobject can call methods from all three levels of the hierarchy.
Inheritance allows us to structure code logically, reuse existing functionality, and enforce relationships between classes. We now know how to extend classes using extends, share internal state securely using protected, manage initialization with super(), and build deep class hierarchies.