Java Inheritance
Java Inheritance
Inheritance allows one class to acquire the fields and methods of another class. The child class gets everything the parent defined — and can add its own fields, methods, and constructor logic on top. It is the mechanism that turns "write once, reuse everywhere" from a goal into a language feature.
But inheritance is also the most overused OOP concept in Java codebases. Understanding when inheritance is the right choice — and when composition is cleaner — is what separates a developer who knows the syntax from one who designs systems well. Both are tested in interviews; both matter in production.
What Is Inheritance in Java?
Inheritance is an OOP mechanism where a class (the child or subclass) extends another class (the parent or superclass) using the extends keyword. The child class inherits all non-private fields and methods from the parent. It can:
- ›Use inherited fields and methods directly
- ›Override inherited methods to provide specialised behaviour
- ›Add new fields and methods specific to the child
- ›Call parent methods and constructors using
super
Java supports single inheritance for classes — a class can extend only one other class. Multiple inheritance through classes is not supported in Java. However, a class can implement multiple interfaces, which is how Java achieves multiple-inheritance-like behaviour without the diamond problem.
Types of Inheritance in Java
| Type | Description | Supported |
|---|---|---|
| Single | Class B extends Class A | Yes |
| Multilevel | Class C extends B, B extends A | Yes |
| Hierarchical | Classes B and C both extend Class A | Yes |
| Multiple (classes) | Class C extends both A and B | No — not supported |
| Multiple (interfaces) | Class implements Interface A and B | Yes |
| Hybrid | Combination of the above | Partially — through interfaces |
Single Inheritance — the extends Keyword
The most fundamental form. One child class extends one parent class.
1// File: Vehicle.java
2
3public class Vehicle {
4
5 private final String registrationNumber;
6 private final String brand;
7 private int speedKmh;
8
9 public Vehicle(String registrationNumber, String brand) {
10 this.registrationNumber = registrationNumber;
11 this.brand = brand;
12 this.speedKmh = 0;
13 }
14
15 public String getRegistrationNumber() { return registrationNumber; }
16 public String getBrand() { return brand; }
17 public int getSpeedKmh() { return speedKmh; }
18
19 public void accelerate(int kmh) {
20 if (kmh > 0) speedKmh += kmh;
21 }
22
23 public void brake(int kmh) {
24 speedKmh = Math.max(0, speedKmh - kmh);
25 }
26
27 public String getStatus() {
28 return brand + " [" + registrationNumber + "] — Speed: " + speedKmh + " km/h";
29 }
30}1// File: Car.java
2
3public class Car extends Vehicle {
4
5 private final int numberOfDoors;
6 private boolean airConditionerOn;
7
8 public Car(String registrationNumber, String brand, int numberOfDoors) {
9 super(registrationNumber, brand); // calls Vehicle constructor
10 this.numberOfDoors = numberOfDoors;
11 this.airConditionerOn = false;
12 }
13
14 public int getNumberOfDoors() { return numberOfDoors; }
15 public boolean isAirConditionerOn(){ return airConditionerOn; }
16
17 public void toggleAirConditioner() {
18 airConditionerOn = !airConditionerOn;
19 }
20
21 // Overrides parent's getStatus() to include car-specific details
22 @Override
23 public String getStatus() {
24 return super.getStatus() + " | Doors: " + numberOfDoors
25 + " | AC: " + (airConditionerOn ? "On" : "Off");
26 }
27}1// File: SingleInheritanceDemo.java
2
3public class SingleInheritanceDemo {
4
5 public static void main(String[] args) {
6
7 Car car = new Car("MH01AB1234", "Tata Nexon", 4);
8
9 car.accelerate(60); // inherited from Vehicle
10 car.toggleAirConditioner(); // defined in Car
11
12 System.out.println(car.getStatus()); // overridden in Car
13 System.out.println("Brand: " + car.getBrand()); // inherited getter
14 }
15}Output:
Tata Nexon [MH01AB1234] — Speed: 60 km/h | Doors: 4 | AC: On
Brand: Tata Nexon
Car inherits accelerate(), brake(), getBrand(), and getSpeedKmh() from Vehicle without rewriting them. It adds toggleAirConditioner() for car-specific behaviour and overrides getStatus() to include car details while reusing the parent's status string through super.getStatus().
The super Keyword in Inheritance
super gives a child class access to the parent class's members — its constructor, its fields, and its methods. It is essential when:
- ›The child constructor must initialise parent fields
- ›The child wants to call the parent version of an overridden method
- ›The child needs a parent field that shares a name with a child field
super() — Calling the Parent Constructor
1// File: ElectricCar.java
2
3public class ElectricCar extends Car {
4
5 private final int batteryCapacityKwh;
6 private double chargePercent;
7
8 public ElectricCar(String registrationNumber, String brand,
9 int numberOfDoors, int batteryCapacityKwh) {
10 super(registrationNumber, brand, numberOfDoors); // calls Car constructor
11 this.batteryCapacityKwh = batteryCapacityKwh;
12 this.chargePercent = 100.0;
13 }
14
15 public double getChargePercent() { return chargePercent; }
16
17 public void charge(double percent) {
18 chargePercent = Math.min(100.0, chargePercent + percent);
19 }
20
21 @Override
22 public void accelerate(int kmh) {
23 if (chargePercent <= 5.0) {
24 System.out.println("Battery critically low — cannot accelerate.");
25 return;
26 }
27 super.accelerate(kmh); // reuses Vehicle's accelerate logic
28 chargePercent -= (kmh * 0.1); // drains battery
29 chargePercent = Math.max(0, chargePercent);
30 }
31
32 @Override
33 public String getStatus() {
34 return super.getStatus() // calls Car's getStatus(), which calls Vehicle's
35 + " | Battery: " + String.format("%.1f", chargePercent) + "%"
36 + " | Capacity: " + batteryCapacityKwh + " kWh";
37 }
38}1// File: SuperKeywordDemo.java
2
3public class SuperKeywordDemo {
4
5 public static void main(String[] args) {
6
7 ElectricCar ev = new ElectricCar("KA05CD5678", "Tata Nexon EV", 4, 40);
8
9 System.out.println(ev.getStatus());
10
11 ev.accelerate(80);
12 System.out.println(ev.getStatus());
13 }
14}Output:
Tata Nexon EV [KA05CD5678] — Speed: 0 km/h | Doors: 4 | AC: Off | Battery: 100.0% | Capacity: 40 kWh
Tata Nexon EV [KA05CD5678] — Speed: 80 km/h | Doors: 4 | AC: Off | Battery: 92.0% | Capacity: 40 kWh
ElectricCar calls super(registrationNumber, brand, numberOfDoors) to initialise the Car (and through it, the Vehicle) fields. super.accelerate(kmh) reuses the parent's speed logic rather than duplicating it. super.getStatus() chains through Car.getStatus() → Vehicle.getStatus(), assembling the full status string layer by layer.
Multilevel Inheritance
A chain where C extends B, and B extends A. The topmost parent's members flow down through the chain.
1// Vehicle → Car → ElectricCar (already shown above)
2// This three-level chain demonstrates multilevel inheritance1// File: MultilevelDemo.java
2
3public class MultilevelDemo {
4
5 public static void main(String[] args) {
6
7 ElectricCar ev = new ElectricCar("DL01EF9012", "MG ZS EV", 4, 50);
8
9 // Methods from all three levels are accessible on ev
10 ev.accelerate(40); // Vehicle — level 1
11 ev.toggleAirConditioner(); // Car — level 2
12 ev.charge(5.0); // ElectricCar — level 3
13
14 System.out.println(ev.getStatus()); // overridden at each level
15 System.out.println("Reg: " + ev.getRegistrationNumber()); // Vehicle
16 System.out.println("Doors: " + ev.getNumberOfDoors()); // Car
17 System.out.println("Charge: " + ev.getChargePercent() + "%"); // ElectricCar
18 }
19}Output:
MG ZS EV [DL01EF9012] — Speed: 40 km/h | Doors: 4 | AC: On | Battery: 61.0% | Capacity: 50 kWh
Reg: DL01EF9012
Doors: 4
Charge: 61.0%
ev can access methods from all three levels of the hierarchy. The JVM uses the most specific override — ElectricCar.getStatus() for the full status, Car.toggleAirConditioner() for the AC, and Vehicle.accelerate() (called via super from ElectricCar.accelerate()) for the speed.
Hierarchical Inheritance
Multiple child classes extending the same parent.
1// File: Motorcycle.java
2
3public class Motorcycle extends Vehicle {
4
5 private final String bikeType; // SPORT, CRUISER, COMMUTER
6
7 public Motorcycle(String registrationNumber, String brand, String bikeType) {
8 super(registrationNumber, brand);
9 this.bikeType = bikeType;
10 }
11
12 public String getBikeType() { return bikeType; }
13
14 @Override
15 public String getStatus() {
16 return super.getStatus() + " | Type: " + bikeType;
17 }
18}1// File: HierarchicalDemo.java
2
3public class HierarchicalDemo {
4
5 public static void main(String[] args) {
6
7 // Both Car and Motorcycle extend Vehicle — hierarchical inheritance
8 Car car = new Car("GJ05GH3456", "Maruti Swift", 4);
9 Motorcycle bike = new Motorcycle("RJ14IJ7890", "Royal Enfield", "CRUISER");
10
11 car.accelerate(50);
12 bike.accelerate(70);
13
14 System.out.println(car.getStatus());
15 System.out.println(bike.getStatus());
16 }
17}Output:
Maruti Swift [GJ05GH3456] — Speed: 50 km/h | Doors: 4 | AC: Off
Royal Enfield [RJ14IJ7890] — Speed: 70 km/h | Type: CRUISER
Car and Motorcycle are siblings — both inherit from Vehicle independently. Changes to Vehicle propagate to both without touching either child class.
How Inheritance Works Internally
The Object Class
Every class in Java implicitly extends java.lang.Object if no explicit extends is written. Object is the root of every class hierarchy in Java. It provides equals(), hashCode(), toString(), getClass(), and clone() — methods available on every Java object.
java.lang.Object
|
Vehicle
/ \
Car Motorcycle
|
ElectricCar
Constructor Chain — What Happens on new ElectricCar(...)
new ElectricCar(...) is called
ElectricCar() calls super() → Car()
Car() calls super() → Vehicle()
Vehicle() calls super() → Object()
Object() constructor runs — completes
Vehicle() body runs — registers fields, speedKmh = 0
Car() body runs — numberOfDoors, airConditionerOn = false
ElectricCar() body runs — batteryCapacityKwh, chargePercent = 100.0
Object is fully initialised — returned to caller
Every constructor in the chain must run before the child's body executes. This is why super() must be the first statement in a constructor — it guarantees parent initialisation happens before child fields are set.
Method Resolution — Which Override Runs?
When you call a method on an object, the JVM uses dynamic dispatch — it looks at the actual runtime type of the object, not the reference type, and finds the most specific override.
1Vehicle ref = new ElectricCar("TN01KL1234", "Ola S1 Pro", 0, 3);
2ref.accelerate(30); // calls ElectricCar.accelerate() — not Vehicle.accelerate()Even though ref is declared as type Vehicle, the method that runs is ElectricCar.accelerate() because that is the actual type of the object. This is runtime polymorphism — and it requires inheritance to work.
Method Overriding Rules
Overriding allows a child class to provide a specific implementation of a method inherited from the parent.
| Rule | Description |
|---|---|
| Same method signature | Name, parameter types, and count must match exactly |
| Same or wider return type | Return type must be the same or a subtype (covariant return) |
| Same or less restrictive access | Cannot reduce visibility (public → protected is illegal) |
| No new checked exceptions | Cannot throw checked exceptions not declared in the parent |
| @Override annotation | Not required but strongly recommended — compiler catches mistakes |
| Cannot override final | Methods declared final in the parent cannot be overridden |
| Cannot override static | Static methods are hidden, not overridden — not polymorphic |
| Cannot override private | Private methods are not inherited — not visible to child |
Inheritance vs Composition
This is the most important design decision related to inheritance — and the most consistently tested in product-based company interviews.
| Aspect | Inheritance | Composition |
|---|---|---|
| Relationship | "is-a" — Car IS A Vehicle | "has-a" — Car HAS AN Engine |
| Coupling | Tight — child depends on parent's internals | Loose — component is replaceable |
| Reuse mechanism | Automatic — inherits all non-private members | Explicit — delegates to the component |
| Flexibility | Low — hierarchy fixed at compile time | High — behaviour swappable at runtime |
| Encapsulation | Weaker — child can access protected parent members | Stronger — component is fully encapsulated |
| Overriding | Built-in | Requires delegation or interface |
| When to use | Genuine "is-a" relationship, stable hierarchy | Code reuse without a type relationship |
| Testing | Harder — child tests depend on parent behaviour | Easier — component can be mocked |
| Java limitation | Single class inheritance only | No limit — can compose many types |
Rule of thumb: If you cannot honestly say "a Child IS A Parent" in the domain, use composition. A Car is genuinely a Vehicle. A Logger is not a UserService — even if UserService needs logging. Give UserService a Logger field; do not extend it.
Composition Example — the Right Alternative
1// File: PaymentLogger.java
2
3public class PaymentLogger {
4
5 public void log(String message) {
6 System.out.println("[PAYMENT LOG] " + message);
7 }
8
9 public void logError(String error) {
10 System.out.println("[PAYMENT ERROR] " + error);
11 }
12}1// File: PaymentService.java
2
3public class PaymentService {
4
5 private final String serviceId;
6 // Composition — PaymentService HAS A PaymentLogger, not IS A PaymentLogger
7 private final PaymentLogger logger;
8
9 public PaymentService(String serviceId, PaymentLogger logger) {
10 this.serviceId = serviceId;
11 this.logger = logger;
12 }
13
14 public boolean processPayment(String orderId, double amount) {
15 logger.log("Processing Rs." + amount + " for " + orderId);
16
17 if (amount <= 0) {
18 logger.logError("Invalid amount: " + amount + " for " + orderId);
19 return false;
20 }
21
22 // ... actual payment processing logic ...
23 logger.log("Payment confirmed for " + orderId);
24 return true;
25 }
26}1// File: CompositionDemo.java
2
3public class CompositionDemo {
4
5 public static void main(String[] args) {
6
7 PaymentLogger logger = new PaymentLogger();
8 PaymentService service = new PaymentService("PAY-SVC-01", logger);
9
10 service.processPayment("ORD-2001", 1499.0);
11 System.out.println();
12 service.processPayment("ORD-2002", -50.0);
13 }
14}Output:
[PAYMENT LOG] Processing Rs.1499.0 for ORD-2001
[PAYMENT LOG] Payment confirmed for ORD-2001
[PAYMENT LOG] Processing Rs.-50.0 for ORD-2002
[PAYMENT ERROR] Invalid amount: -50.0 for ORD-2002
PaymentService uses a PaymentLogger without extending it. The logger can be swapped — in tests, a mock logger can be passed instead of the real one. The relationship is correct: a PaymentService is not a PaymentLogger, so it should not inherit from it.
Real-World Example — Employee Payroll System
The Business Problem
You are building the payroll calculation module for a staffing company — similar to what companies like Infosys or Wipro use for their HR systems. The system has a base Employee type with common fields and a basic salary calculation. FullTimeEmployee and ContractEmployee extend it with different pay structures. Both share common behaviour from Employee but calculate getMonthlyPay() differently.
Implementation
1// File: PayrollConfig.java
2
3public final class PayrollConfig {
4
5 public static final double PF_RATE = 0.12;
6 public static final double DA_RATE = 0.10;
7 public static final double HRA_RATE = 0.15;
8
9 private PayrollConfig() {}
10}1// File: Employee.java
2
3public abstract class Employee {
4
5 private final String employeeId;
6 private final String fullName;
7 private final String department;
8
9 public Employee(String employeeId, String fullName, String department) {
10 this.employeeId = employeeId;
11 this.fullName = fullName;
12 this.department = department;
13 }
14
15 public String getEmployeeId() { return employeeId; }
16 public String getFullName() { return fullName; }
17 public String getDepartment() { return department; }
18
19 // Abstract — each subclass computes pay differently
20 public abstract double getMonthlyPay();
21
22 // Common payslip format — uses abstract method polymorphically
23 public String getPaySlip() {
24 return String.format("%-12s | %-22s | %-20s | Monthly Pay: Rs.%.2f",
25 employeeId, fullName, department, getMonthlyPay());
26 }
27}1// File: FullTimeEmployee.java
2
3public class FullTimeEmployee extends Employee {
4
5 private final double basicSalary;
6
7 public FullTimeEmployee(String employeeId, String fullName,
8 String department, double basicSalary) {
9 super(employeeId, fullName, department);
10 this.basicSalary = basicSalary;
11 }
12
13 public double getBasicSalary() { return basicSalary; }
14
15 @Override
16 public double getMonthlyPay() {
17 double da = basicSalary * PayrollConfig.DA_RATE;
18 double hra = basicSalary * PayrollConfig.HRA_RATE;
19 double pf = basicSalary * PayrollConfig.PF_RATE;
20 return basicSalary + da + hra - pf;
21 }
22}1// File: ContractEmployee.java
2
3public class ContractEmployee extends Employee {
4
5 private final double dailyRate;
6 private final int daysWorked;
7
8 public ContractEmployee(String employeeId, String fullName,
9 String department, double dailyRate, int daysWorked) {
10 super(employeeId, fullName, department);
11 this.dailyRate = dailyRate;
12 this.daysWorked = daysWorked;
13 }
14
15 public double getDailyRate() { return dailyRate; }
16 public int getDaysWorked() { return daysWorked; }
17
18 @Override
19 public double getMonthlyPay() {
20 // Contract employees: daily rate × days worked, no PF or allowances
21 return dailyRate * daysWorked;
22 }
23}1// File: PayrollDemo.java
2
3import java.util.List;
4
5public class PayrollDemo {
6
7 public static void main(String[] args) {
8
9 List<Employee> employees = List.of(
10 new FullTimeEmployee("EMP-001", "Ananya Krishnan",
11 "Backend Engineering", 85000.0),
12 new FullTimeEmployee("EMP-002", "Rohan Desai",
13 "DevOps", 72000.0),
14 new ContractEmployee("CON-001", "Priya Mehta",
15 "QA Automation", 3500.0, 22),
16 new ContractEmployee("CON-002", "Karan Singh",
17 "UI Design", 4000.0, 18)
18 );
19
20 System.out.println("=== Monthly Payroll Report ===\n");
21 double totalPayroll = 0;
22
23 for (Employee emp : employees) {
24 System.out.println(emp.getPaySlip());
25 totalPayroll += emp.getMonthlyPay();
26 }
27
28 System.out.printf("%n Total payroll this month: Rs.%.2f%n", totalPayroll);
29 }
30}Output:
=== Monthly Payroll Report ===
EMP-001 | Ananya Krishnan | Backend Engineering | Monthly Pay: Rs.96050.00
EMP-002 | Rohan Desai | DevOps | Monthly Pay: Rs.81360.00
CON-001 | Priya Mehta | QA Automation | Monthly Pay: Rs.77000.00
CON-002 | Karan Singh | UI Design | Monthly Pay: Rs.72000.00
Total payroll this month: Rs.326410.00
The for loop works on Employee references — it does not know whether each one is FullTimeEmployee or ContractEmployee. getMonthlyPay() is called polymorphically — the JVM dispatches to the correct subclass implementation. getPaySlip() is defined once in Employee and reused by both subclasses without any duplication.
Best Practices
Use inheritance only for genuine "is-a" relationships
Before adding extends, ask: "Is a Child genuinely a specific kind of Parent in the domain?" A Car is a Vehicle. A SavingsAccount is an Account. A PaymentService is not a Logger. If the answer is no — if you are extending only for code reuse — use composition instead. Inheritance creates a tight coupling that is difficult to reverse once other code depends on the hierarchy.
Keep inheritance hierarchies shallow
Beyond two or three levels, inheritance hierarchies become difficult to reason about. When D extends C extends B extends A, a change to A must be evaluated against B, C, and D simultaneously. Teams following clean architecture treat hierarchies deeper than three levels as a design smell requiring justification.
Always use @Override when overriding methods
The @Override annotation tells the compiler to verify that the method actually overrides something in the parent. Without it, a typo in the method name — getMonthlyPay() accidentally spelled getMonthlyPat() — silently creates a new method instead of overriding, which is a runtime bug that compiles cleanly. Every override in production Java should have @Override.
Do not override methods to do nothing or to throw exceptions
If a subclass must override a parent method to make it do nothing (return immediately) or throw UnsupportedOperationException, the hierarchy is wrong. The child is not a true subtype — it violates the Liskov Substitution Principle. Redesign using composition or an alternative hierarchy.
Common Mistakes
Mistake 1 — Forgetting super() When the Parent Has No No-Arg Constructor
1public class Account {
2 private final String accountId;
3
4 // Only a parameterised constructor — no default no-arg constructor
5 public Account(String accountId) {
6 this.accountId = accountId;
7 }
8}
9
10public class SavingsAccount extends Account {
11
12 private double interestRate;
13
14 public SavingsAccount(String accountId, double interestRate) {
15 // Missing super(accountId) — compiler inserts super() but Account has none
16 // This causes: "constructor Account in class Account cannot be applied to no arguments"
17 this.interestRate = interestRate;
18 }
19}When the parent has no no-arg constructor, the child must explicitly call super(args) as the first statement. The compiler cannot auto-insert super() if the parent does not have a no-argument version.
Mistake 2 — Using Inheritance for Code Reuse Without an "is-a" Relationship
1// Wrong design — Stack IS NOT an ArrayList conceptually
2// But java.util.Stack extends Vector (a real Java library mistake)
3public class Stack<T> extends ArrayList<T> {
4 // Now Stack inherits add(), remove(), get() and all List methods
5 // Callers can do stack.add(0, element) — inserting at an arbitrary index
6 // This breaks the Stack contract (LIFO only)
7}
8
9// Correct — composition
10public class Stack<T> {
11 private final ArrayList<T> storage = new ArrayList<>();
12
13 public void push(T element) { storage.add(element); }
14 public T pop() { return storage.remove(storage.size() - 1); }
15 public T peek() { return storage.get(storage.size() - 1); }
16 public boolean isEmpty() { return storage.isEmpty(); }
17}java.util.Stack extending Vector is a historical mistake in the Java standard library — it exposes List methods that violate stack semantics. A Stack is not truly an ArrayList. This is why Deque implementations like ArrayDeque are now recommended over Stack for stack behaviour in Java.
Mistake 3 — Reducing Visibility of an Overridden Method
1public class Parent {
2 public void display() {
3 System.out.println("Parent display");
4 }
5}
6
7public class Child extends Parent {
8 // Compile error — cannot reduce visibility from public to protected
9 @Override
10 protected void display() { // error: weaker access privileges
11 System.out.println("Child display");
12 }
13}An overriding method cannot be less visible than the parent method. If the parent declares public, the override must be public. Reducing visibility breaks the contract that any caller who has a Parent reference can call display() — if the override is protected, subclass code in other packages could not.
Mistake 4 — Calling Overridable Methods in a Constructor
1public class Base {
2 public Base() {
3 initialise(); // dispatches to Child.initialise() — but Child fields not set yet
4 }
5 public void initialise() { System.out.println("Base initialise"); }
6}
7
8public class Child extends Base {
9 private String name;
10
11 public Child(String name) {
12 super(); // Base() runs first — calls initialise() before name is set
13 this.name = name;
14 }
15
16 @Override
17 public void initialise() {
18 // name is null here — Base constructor called this before Child's body ran
19 System.out.println("Child initialise: " + name);
20 }
21}When new Child("Alice") runs, super() calls Base(), which calls initialise(). Java dispatches to Child.initialise() because the runtime type is Child. But name has not been assigned yet — the Child constructor body has not run. The result is Child initialise: null. Never call overridable methods from constructors.
Interview Questions
Q1. What is inheritance in Java and what does the extends keyword do?
Inheritance is the OOP mechanism where a class acquires the fields and methods of another class. The extends keyword establishes this relationship — the child class inherits all non-private members of the parent and can add its own. Java supports single class inheritance only — a class can extend exactly one other class. Every class implicitly extends java.lang.Object if no explicit extends is written, making Object the root of every Java class hierarchy.
Q2. What is the difference between method overriding and method hiding in Java?
Method overriding applies to instance methods — when a child class declares a method with the same signature as a parent instance method, it overrides it. The JVM uses dynamic dispatch at runtime to call the most specific override based on the object's actual type. Method hiding applies to static methods — a child static method with the same signature hides the parent's static method. No dynamic dispatch occurs — the method called is determined by the reference type at compile time, not the actual object type. Overriding is polymorphic; hiding is not.
Q3. What is the difference between inheritance and composition in Java?
Inheritance models an "is-a" relationship — Car extends Vehicle means a Car is a Vehicle. Composition models a "has-a" relationship — a Car has an Engine field. Inheritance gives automatic access to parent behaviour but creates tight coupling — changes to the parent ripple to all children. Composition is looser — the component is replaceable and independently testable. Composition should be preferred when the relationship is about code reuse without a genuine type hierarchy. Inheritance is appropriate when the "is-a" relationship is real and stable.
Q4. What is the purpose of the super keyword in Java inheritance?
super provides access to the parent class from within a child class. super() calls the parent's constructor and must be the first statement in the child constructor — it is required when the parent has no no-arg constructor and recommended always for clarity. super.method() calls the parent's version of an overridden method, allowing the child to extend rather than completely replace the parent's behaviour. super.field accesses a parent field that is shadowed by a child field of the same name.
Q5. What is the Liskov Substitution Principle and how does it relate to inheritance?
The Liskov Substitution Principle states that any code that works with a parent type reference must work correctly with a child type reference substituted in its place — without knowing the difference. A well-designed inheritance hierarchy satisfies this: every Car can be used wherever a Vehicle is expected. When a subclass overrides a method to throw an exception, to do nothing, or to change the contract, it violates LSP. This is a signal that the "is-a" relationship is wrong and composition would be more appropriate.
Q6. Does Java support multiple inheritance? How does it handle the diamond problem?
Java does not support multiple inheritance through classes — a class can extend only one other class. This eliminates the diamond problem for classes: if B and C both extend A and override a method, and D extends both B and C, there is no ambiguity — the Java compiler simply does not allow it. Java achieves multiple-inheritance-like behaviour through interfaces, which a class can implement multiple of. When two interfaces provide default method implementations with the same signature, the implementing class must override the method to resolve the ambiguity explicitly — the compiler enforces this.
FAQs
What is inheritance in Java?
Inheritance is the mechanism where a class acquires the fields and methods of another class using the extends keyword. The child class inherits all non-private members, can override inherited methods to specialise behaviour, and can add its own members. It enables code reuse and is the foundation of polymorphism in Java.
What is the difference between single and multilevel inheritance?
Single inheritance is when one class extends exactly one other class — Car extends Vehicle. Multilevel inheritance is a chain — ElectricCar extends Car, Car extends Vehicle. In multilevel inheritance, members flow through the entire chain — ElectricCar inherits from both Car and Vehicle. Java supports both. Multiple inheritance (one class extending two or more classes directly) is not supported for classes.
Can a constructor be inherited in Java?
No. Constructors are not inherited. A child class must define its own constructors. However, a child constructor can — and in many cases must — call the parent's constructor using super() as the first statement. If no explicit super() call is made, the compiler automatically inserts super() (the no-arg parent constructor). If the parent has no no-arg constructor, the child must explicitly call the appropriate parameterised parent constructor.
What is the difference between overriding and overloading?
Overriding is redefining a method inherited from a parent class in the child class — same name, same parameter list, different implementation. It is resolved at runtime (dynamic dispatch). Overloading is defining multiple methods in the same class with the same name but different parameter lists. It is resolved at compile time based on argument types. Overriding enables polymorphism; overloading is a compile-time convenience.
Why does Java not support multiple inheritance through classes?
Java's designers excluded multiple class inheritance to avoid the diamond problem — the ambiguity that arises when two parent classes both define the same method and a class inherits from both. Rather than resolving this ambiguity with complex rules, Java chose a cleaner model: single class inheritance plus multiple interface implementation. Interfaces can have default methods, and when two interfaces conflict on a default method, the implementing class must explicitly resolve it.
What is the difference between IS-A and HAS-A relationships?
IS-A is the inheritance relationship — Car IS A Vehicle, modelled with extends. It means the child is a specific type of the parent and can be used wherever the parent type is expected. HAS-A is the composition relationship — a Car HAS AN Engine, modelled by making Engine a field of Car. It means the class uses another class's functionality without being a type of it. IS-A should be used only when the relationship is genuinely hierarchical in the domain.
Summary
Inheritance gives a child class automatic access to everything a parent defines — fields, methods, and the type relationship that enables polymorphism. The constructor chain guarantees parent initialisation before child setup. Method overriding with dynamic dispatch allows one method call to behave differently depending on the actual object type — the engine of runtime polymorphism.
The discipline of inheritance is knowing when not to use it. When the "is-a" relationship is not genuine, composition produces looser coupling, easier testing, and more flexible design. The rule is simple: extend when a child truly is a parent; compose when it uses a parent.
For interviews, be ready to explain the types of inheritance Java supports and why multiple class inheritance is excluded, demonstrate the constructor chain with super(), articulate the difference between overriding and hiding with concrete examples, and compare inheritance versus composition with a real design decision. These points cover the depth that service-based recall questions and product-based design discussions both probe.
What to Read Next
| Topic | Link |
|---|---|
| How polymorphism uses inheritance for runtime method dispatch | Java Polymorphism → |
| How method overriding rules and @Override work in detail | Java Method Overriding → |
| How abstract classes define partial hierarchies for inheritance | Java Abstract Classes → |
| How interfaces enable multiple-inheritance-like behaviour | Java Interfaces → |
| How the super keyword accesses parent members and constructors | Java super Keyword → |