Inheritance
A complete guide to inheritance in Java: the extends keyword, single and multilevel hierarchies, the diamond problem, the super keyword, method overriding, covariant return types, constructors in inheritance, final classes and methods, the Object root class, and the instanceof operator.
The Inheritance Concept
Inheritance is a mechanism that lets one class acquire the fields and methods of another. The class that is inherited from is called the parent class, base class, or superclass. The class that inherits is called the child class, derived class, or subclass. The child gets everything the parent declared as non-private, and it can then add its own fields and methods on top or modify the ones it inherits.
The practical benefit is code reuse. If you are building a system with classes for Dog, Cat, and Bird, all three share attributes like a name, an age, and behaviours like eating. Rather than writing those three times, you write them once in an Animal class and have the three classes inherit from it. Each subclass then only contains what is unique to it.
IS-A relationship
Inheritance models an IS-A relationship between classes. Before extending a class, ask the question honestly.
- A Dog IS-A Animal: valid. Dog extends Animal makes sense.
- A Car IS-A Vehicle: valid. Car extends Vehicle makes sense.
- A Stack IS-A ArrayList: questionable. Just because a Stack could be implemented using an ArrayList does not make it conceptually an ArrayList. Misusing inheritance for implementation convenience creates confusing APIs.
- If the IS-A test fails, prefer composition: give your class a field of the other type rather than extending it.
The extends Keyword
Inheritance in Java is declared with the extends keyword in the class declaration. The child class lists the parent class after extends, and from that point on it has access to all non-private members of the parent. You can only name one class after extends in Java, which is one of its fundamental design constraints.
extends Keyword
JavaA basic parent-child relationship where the child inherits fields and methods from the parent.
Single Inheritance
Single inheritance means one class extends exactly one other class. This is the only form of class-based inheritance Java supports, and it is also the most common pattern you will write. The child gains everything from the parent, adds its own specialised behaviour, and the relationship remains clean and easy to follow.
Single Inheritance
JavaA Vehicle parent class and a Car child class demonstrating a clean single-parent relationship.
Multilevel Inheritance
Multilevel inheritance is a chain where class B extends class A, and class C extends class B. Class C inherits from both B and A, gaining access to all non-private members of the entire chain. There is no limit to how deep the chain can go, but in practice chains longer than two or three levels become hard to understand and maintain.
Multilevel Inheritance
JavaA three-level chain: LivingThing, Animal, Dog, where each level adds new behaviour.
Hierarchical Inheritance
Hierarchical inheritance is when multiple child classes all extend the same parent. This is the natural shape for modelling a family of related types that share a common foundation but each branch out in their own direction. A single Shape class with subclasses Circle, Rectangle, and Triangle is a classic example.
Hierarchical Inheritance
JavaThree shape subclasses each inheriting a shared base and providing their own area calculation.
Why Multiple Inheritance is Not Supported: The Diamond Problem
Java does not allow a class to extend more than one class. The reason comes down to a well-known ambiguity called the diamond problem. Consider four classes: A at the top defines a method greet(). B extends A and overrides greet(). C extends A and also overrides greet(). Now D tries to extend both B and C. When you call greet() on a D object, which version should run? B's or C's? The compiler has no principled way to decide, and so Java simply disallows the situation.
How Java solves the need for multiple types
Java sidesteps the diamond problem by separating the concept of type from the concept of implementation.
- Interfaces provide multiple type inheritance without method implementation conflicts (before Java 8). A class can implement as many interfaces as it needs.
- Since Java 8, interfaces can have default methods with implementations. If two interfaces provide conflicting defaults, the implementing class must explicitly override the conflicting method to resolve the ambiguity, which the compiler enforces.
- This design gives Java the flexibility of multiple inheritance at the type level while avoiding the ambiguity at the implementation level.
The Diamond Problem
JavaDemonstrating why C extends A, B is illegal, and how interfaces provide the safe alternative.
The super Keyword
Inside a subclass, super is a reference to the parent class portion of the current object. It allows you to access parent-class members that would otherwise be hidden or overridden by members in the subclass. The two most common uses are calling a parent constructor and calling a parent method that the child has overridden.
Two main uses of super
super resolves ambiguity when a subclass shadows or overrides something from the parent.
- super.fieldName or super.methodName():Accesses the parent version of a field or method. This is useful when the subclass has defined a member with the same name as the parent, and you need both.
- super(args):Calls the parent class constructor. Must be the very first statement in the child constructor. Used to initialise the fields the parent owns before the child initialises its own fields.
The super Keyword
JavaUsing super to call a parent method alongside and extend its behaviour inside the overriding method.
super() Constructor Call
When you create a subclass object, the parent class must be initialised first. Java enforces this by requiring that if you write an explicit super(...) call it must be the very first statement in the child constructor. If you omit it, the compiler inserts super() (a zero-argument call) automatically. If the parent class does not have a no-argument constructor, you must call the appropriate parameterised one explicitly, or you will get a compile error.
super() Constructor Call
JavaTracing the constructor chain through three levels to show the order of initialisation.
Method Overriding
Method overriding is when a subclass provides its own implementation for a method that already exists in the parent class. When the method is called on a subclass object, Java runs the subclass version, not the parent version. This is the mechanism that makes runtime polymorphism work: you can write code against the parent type and the correct subclass behaviour executes automatically at runtime.
Rules for valid method overriding
All four conditions must be met for the compiler to recognise an override.
- Same method name:The overriding method must have exactly the same name as the parent method.
- Same parameter list:The number, types, and order of parameters must match exactly. A different signature creates an overloaded method, not an override.
- Compatible return type:The return type must be the same as or a subtype of the parent method's return type (covariant return types, covered next).
- Same or less restrictive access:You cannot make an overriding method more private. A protected parent method can be overridden as protected or public, but not as private.
Method Overriding
JavaA Payment hierarchy where each subclass provides its own processPayment() implementation.
The @Override Annotation
The @Override annotation tells the compiler: "I intend this method to override a parent class method. Please verify that." If the annotation is present and no matching parent method is found, the compiler produces an error. This catches two common mistakes early: a typo in the method name, and a mismatch in the parameter list.
Why @Override is worth the habit
The annotation is technically optional but skipping it regularly leads to subtle bugs.
- Without @Override, a typo like toString() becoming tostring() silently creates a new method instead of overriding Object.toString(). Your objects will still print the default cryptic reference when passed to println().
- With @Override, that same typo produces a compile error immediately: "method does not override or implement a method from a supertype."
- When a parent class method is refactored and its signature changes, @Override on all child overrides will flag every affected class at compile time rather than letting silent bugs slip into production.
- Most style guides, including Google's Java Style Guide, require @Override on every overriding method.
@Override Annotation
JavaShowing how @Override catches a method name typo that would otherwise be silently wrong.
Covariant Return Types
Before Java 5, an overriding method had to have exactly the same return type as the parent method. Java 5 introduced covariant return types, which allow the overriding method to return a subtype of what the parent method returns. This lets you write more specific APIs in subclasses without losing the connection to the parent method.
Covariant Return Types
JavaA factory method in the parent returns Animal; the child overrides it to return Dog, which is a subtype.
Constructors in Inheritance
Constructors are not inherited. You cannot call a parent class constructor as if it were your own, and a subclass constructor does not automatically become available just because the parent has one. However, every subclass constructor must ensure the parent class is initialised, which it accomplishes by calling one of the parent's constructors, either explicitly through super() or implicitly when the compiler inserts the no-arg call.
Constructor flow in inheritance
Understanding the order of construction prevents confusion about when fields are ready.
- Static initialisers and static fields of the parent class run first, in source order.
- The parent class constructor body runs next, initialising all of the parent's fields.
- Static initialisers and static fields of the child class run.
- The child class constructor body runs, initialising the child's own fields.
- The result is a fully initialised object: parent portion first, child portion second.
Constructor Chain in Inheritance
JavaA three-level hierarchy showing when each constructor body executes.
final Class and final Method in Inheritance
The final keyword is the mechanism for locking down inheritance at either the class or the method level. It is worth revisiting here in the context of inheritance because it is the direct answer to the question "how do I prevent a subclass from changing this behaviour?"
final in the context of inheritance
final is used when you have a deliberate reason to close a part of the class hierarchy.
- final class:No class can extend it. String, Integer, and all wrapper types in Java are final. Marking a class final is a strong statement: you are saying the abstraction is complete and should not be specialised further.
- final method:The method cannot be overridden in any subclass. The class itself can still be extended; only that specific method is locked. Use this when overriding would break a class invariant or security guarantee.
- Performance note:The JVM can sometimes apply optimisations to final methods because it knows the exact implementation that will run. This advantage is rarely significant compared to the design clarity final provides.
final Class and final Method
JavaA final method that guarantees tamper-proof audit logging regardless of subclass behaviour.
The Object Class as Root of the Hierarchy
Every class in Java, whether you write it or it comes from a library, implicitly extends java.lang.Object if no other parent is specified. This means Object is the root of every class hierarchy in Java. Any variable of type Object can hold a reference to any object of any type, which is why methods like System.out.println() can accept any argument: they accept Object and call toString() on it.
Key methods inherited from Object
These methods are available on every object you ever create in Java.
- toString():Returns a String representation. Override it to make debugging output useful.
- equals(Object o):Content equality test. Override it to define what equality means for your class.
- hashCode():Returns an integer hash. Must be overridden consistently with equals() for correct behaviour in hash-based collections.
- getClass():Returns the runtime Class object. Cannot be overridden.
- clone():Creates and returns a copy of the object. Requires implementing Cloneable.
- wait(), notify(), notifyAll():Thread coordination methods used with synchronised blocks in multithreading.
Object as the Universal Base Type
JavaDemonstrating that any object can be stored in an Object variable and how polymorphism flows from this.
The instanceof Operator in Inheritance
The instanceof operator tests whether an object is an instance of a particular type, including any type in its inheritance chain. The result is true if the object is of that type or any subtype of it. It always returns false for a null reference, which makes it safe to call without a prior null check.
Pattern matching for instanceof (Java 16+)
Modern Java removes the manual cast that traditionally follows an instanceof check.
- Traditional pattern:if (obj instanceof Dog) { Dog d = (Dog) obj; d.bark(); }, you check the type and then cast in a separate step.
- Pattern matching (Java 16+):if (obj instanceof Dog d) { d.bark(); }, if the check passes, d is automatically bound to the Dog reference in the same expression. No explicit cast needed.
- Why instanceof matters in inheritance:In a polymorphic hierarchy where you only have a parent-type reference, instanceof lets you safely discover and use subtype-specific behaviour without catching a ClassCastException.
instanceof in Inheritance
JavaChecking membership at multiple levels and using pattern matching for safe downcasting.
Quiz - Test Your Knowledge
Ten questions covering the inheritance concept, extends, multilevel and hierarchical hierarchies, the diamond problem, super, method overriding, @Override, covariant return types, constructors, final, the Object root class, and instanceof. Read each option carefully before selecting your answer.
Knowledge Check
1. Which keyword is used to establish an inheritance relationship between two classes in Java?
2. In multilevel inheritance, if class C extends B and B extends A, which class is at the top of the chain?
3. What is the Diamond Problem in Java?
4. What does super() do when used inside a constructor?
5. What is required for a method in a subclass to correctly override a method in its parent class?
6. What is the purpose of the @Override annotation?
7. What is a covariant return type?
8. When a subclass constructor does not explicitly call super(), what happens?
9. Which statement about the instanceof operator is correct?
10. Every class in Java implicitly extends which class if no parent is specified?