Inheritance
Inheritance is one of the central ideas in object-oriented programming, and in Java it is the mechanism that allows one class to build on the structure and behavior of another class. When a class inherits from another class, the new class can reuse accessible fields and methods from the existing class instead of defining the same logic again. The existing class is commonly called the parent class, superclass, or base class. The new class is called the child class, subclass, or derived class. This relationship helps developers model real-world hierarchies, reduce repeated code, and extend existing behavior in a controlled way.
In Java, inheritance is created with the extends keyword. A subclass extends a superclass, which
means the subclass receives the accessible members of the superclass and can add its own members on top of
them. This is why inheritance is often described as an "IS-A" relationship. If Dog extends
Animal, then a dog is an animal. If SavingsAccount extends Account,
then a savings account is an account. This relationship is powerful, but it must be used carefully because
it connects classes closely. Good inheritance design is not just about reusing code; it is about representing
a meaningful conceptual relationship.
Inheritance is also a high-frequency Java interview topic because it connects directly to polymorphism,
method overriding, constructor chaining, access modifiers, the super keyword, field hiding, and
runtime method dispatch. A developer who understands inheritance properly can explain not only how to write
class B extends A, but also what happens when a child object is referenced by a parent variable,
why constructors are not inherited, why private members are not directly accessible, and why Java does not
support multiple inheritance through classes. These details are important because inheritance affects both
code structure and runtime behavior.
What Is Inheritance?
Inheritance means that one class acquires the accessible properties and behaviors of another class. In practical Java terms, this means a child class can use non-private methods and fields defined in its parent class as if they are part of the child class. The child class can also define additional fields and methods that are specific to its own responsibility. This allows common behavior to live in one place while specific behavior is added in specialized classes.
For example, consider a general Vehicle class. It may contain common behavior such as
start(), stop(), and move(). A Car class and a
Bike class may both share those behaviors, but each may also have specific behavior of its own.
Instead of copying the same methods into every class, Java allows those classes to inherit common behavior
from the parent class. This keeps the design easier to maintain because a common change can be made in one
superclass instead of being repeated across multiple classes.
The key point is that inheritance should describe a natural relationship. A subclass should be a specialized
form of the superclass. If that relationship is not true, inheritance may make the code harder to understand
instead of easier. A Car is a Vehicle, so inheritance makes sense. A
Car has an Engine, but it is not an engine, so composition is usually a better
design for that relationship. This difference between "is a" and "has a" is one of the most important
design judgments in object-oriented programming.
class Parent {
int x = 10;
}
class Child extends Parent {
int y = 20;
}
In this example, Child extends Parent. The child class has its own variable
y, and it also has access to the inherited variable x because x is not
private. Conceptually, Java treats the child object as an extended version of the parent object. The child
object contains the parent portion and the child-specific portion together. That is why a child object can be
used in many places where a parent type is expected.
Why Inheritance Is Important
The first obvious benefit of inheritance is code reuse. If several classes share the same behavior, that behavior can be placed in a superclass and reused by all child classes. This reduces duplication and makes future changes simpler. When common logic is copied into many classes, every correction must be applied in many places. Inheritance centralizes the shared logic so the system remains easier to maintain.
Inheritance also supports extensibility. A developer can create a new subclass that reuses existing behavior while adding new behavior for a specific case. This is common in frameworks and libraries. A framework may provide a general base class, and application developers extend that class to add project-specific behavior. The base class provides structure, while subclasses provide specialization. This pattern allows code to grow without constantly modifying existing classes.
Another major reason inheritance matters is polymorphism. Polymorphism allows a parent reference to point to
a child object. When overridden methods are called, Java can choose the child implementation at runtime. This
enables flexible designs where code can work with a general type while the actual behavior depends on the
specific object. For example, a method may accept an Animal reference, but at runtime the object
may be a Dog, Cat, or Bird. Each subclass can provide its own version
of a method such as sound().
Inheritance also makes software models easier to understand when the domain itself has a hierarchy. In a
banking application, Account may be the parent class, while SavingsAccount,
CurrentAccount, and LoanAccount are child classes. In an employee management
system, Employee may be the parent class, while Manager,
Developer, and Tester are specialized child classes. When the code structure
mirrors the business structure, the system becomes easier to reason about.
Basic Syntax
Java uses the extends keyword to define class inheritance. The subclass name appears first,
followed by extends, followed by the superclass name. A class can extend only one class directly.
This restriction is deliberate because allowing a class to inherit implementation from multiple parent
classes can create ambiguity when two parents define the same method or field.
class SubClass extends SuperClass {
// additional members
}
The superclass should contain behavior that is genuinely common to all subclasses. The subclass should contain behavior that is specific to that specialized type. If every subclass constantly overrides most of the superclass behavior, the hierarchy is probably poorly designed. Good inheritance makes common behavior common and specialized behavior specialized.
Example: Simple Inheritance
The simplest way to understand inheritance is to start with a parent class that defines one general behavior
and a child class that adds one specific behavior. In the following example, Animal defines the
common action eat(). The Dog class extends Animal and adds
bark(). When a Dog object is created, it can call both methods because it owns
bark() and inherits eat().
class Animal {
void eat() {
System.out.println("Animal eats");
}
}
class Dog extends Animal {
void bark() {
System.out.println("Dog barks");
}
}
public class Test {
public static void main(String[] args) {
Dog d = new Dog();
d.eat(); // inherited
d.bark(); // own method
}
}
This example demonstrates the basic reuse benefit. The Dog class does not need to rewrite the
eat() method because it receives that method from Animal. If another class such as
Cat also extends Animal, it can reuse the same method too. In larger applications,
this same idea helps teams avoid repeated business logic across many related classes.
Types of Inheritance in Java
1. Single Inheritance
Single inheritance means one child class inherits from one parent class. This is the most common and most
straightforward inheritance form in Java. It keeps the relationship easy to understand because there is a
direct line from child to parent. When you see class B extends A, you know that B
receives accessible members from A.
class B extends A { }
Status: Supported in Java.
2. Multilevel Inheritance
Multilevel inheritance means a class inherits from a class that already inherits from another class. This
creates a chain. In the structure A, B, and C, class
B extends A, and class C extends B. As a result,
C can access eligible members from both B and A. This is useful when
each level adds a meaningful layer of specialization.
class A { }
class B extends A { }
class C extends B { }
Status: Supported in Java.
Multilevel inheritance should not be stretched too far. Very deep inheritance chains can make code difficult to trace because behavior may come from several levels above the current class. A small, meaningful hierarchy is useful. A long chain used only to share scattered code becomes a maintenance problem.
3. Hierarchical Inheritance
Hierarchical inheritance means multiple child classes inherit from the same parent class. This is common when
several specialized classes share a common base. For example, Car, Truck, and
Bike may all extend Vehicle. Each child class reuses common vehicle behavior while
defining its own specific details.
class A { }
class B extends A { }
class C extends A { }
Status: Supported in Java.
4. Multiple Inheritance (Classes)
class C extends A, B { } // Not allowed
Multiple inheritance through classes means one class directly inherits from more than one parent class. Java does not allow this. A Java class can extend only one class directly. This avoids ambiguity when two parent classes contain methods with the same signature or state with conflicting meaning. Java supports multiple type inheritance through interfaces instead, where a class can implement more than one interface.
Status: Not supported using classes. It is achieved using interfaces.
extends Keyword
The extends keyword is the language feature that declares inheritance between two classes. It
tells the compiler that the subclass should receive eligible members from the superclass. It also establishes
type compatibility. A subclass object can be assigned to a superclass reference because the child is treated
as a specialized version of the parent.
This type compatibility is a major reason inheritance is more than code reuse. It allows APIs to be written
against general parent types while still accepting specialized child objects. For example, a method can
receive an Animal parameter and work with any class that extends Animal. This
creates flexible code because the method does not need separate overloads for every child type.
At the same time, extends should not be used just because two classes share a few methods. If
there is no true parent-child relationship, inheritance creates a misleading design. In Java, a misleading
hierarchy often becomes painful when requirements change, because child classes are forced to carry behavior
they do not actually need.
What Is Inherited and What Is Not
A child class inherits accessible members from its parent class. Public members are inherited and accessible from anywhere. Protected members are inherited and can be accessed inside subclasses, even from different packages. Default members, also called package-private members, are accessible only when the subclass is in the same package as the parent class. Private members are not directly inherited in the usable sense because they cannot be accessed directly from the child class.
This does not mean private data disappears from the object. If a parent class has private fields, those fields still exist inside the parent portion of the child object. However, the child class cannot access them directly by name. The child must use public or protected methods provided by the parent class, such as getters, setters, or protected helper methods. This is how inheritance works together with encapsulation: subclasses can reuse behavior without breaking the parent class's data protection.
Constructors are not inherited. A constructor belongs to the class in which it is declared, and its purpose
is to initialize objects of that exact class. When a child object is created, Java still executes a parent
constructor first, but the child class does not inherit the parent constructor as a callable member. The child
constructor either calls super() implicitly or calls a specific parent constructor explicitly.
Constructor Behavior in Inheritance
Constructor execution is one of the most important details to understand in inheritance. When a child object
is created, Java must initialize the parent part of the object before initializing the child part. Therefore,
the parent constructor runs first, followed by the child constructor. If the child constructor does not
explicitly call a parent constructor, Java inserts a call to super() automatically as the first
statement.
class A {
A() {
System.out.println("A constructor");
}
}
class B extends A {
B() {
System.out.println("B constructor");
}
}
Output:
A constructor
B constructor
In this example, creating an object of class B first executes the constructor of class
A. Only after the parent initialization is complete does Java run the constructor body of
B. This order is logical because the child class may depend on fields or setup provided by the
parent class.
If the parent class has only a parameterized constructor and no no-argument constructor, the child class must
explicitly call the parent constructor using super(arguments). Otherwise, the compiler tries to
insert super(), but no matching no-argument constructor exists. This is a common beginner error
and an important interview point.
super Keyword (Preview)
The super keyword refers to the immediate parent class portion of the current object. It is used
mainly in two situations: calling a parent class constructor and accessing a parent class method or field
when the child class has overridden or hidden a member with the same name. In constructors,
super() or super(arguments) must be the first statement because parent
initialization must happen before child initialization.
super.methodName();
super();
When used with methods, super helps a child class extend parent behavior instead of completely
replacing it. For example, a child method can call super.show() to execute the parent
implementation and then add child-specific logic afterward. This is useful when the parent behavior remains
valid but needs to be enhanced.
Method Overriding (Preview)
Method overriding occurs when a child class provides its own implementation of a method already defined in the parent class. The method name, parameter list, and compatible return type must match the parent method. Overriding is one of the main ways inheritance supports polymorphism. The parent class defines a general contract, and child classes provide specific behavior.
class Animal {
void sound() {
System.out.println("Animal sound");
}
}
class Dog extends Animal {
void sound() {
System.out.println("Dog barks");
}
}
If a parent reference points to a child object and an overridden method is called, Java executes the child version at runtime. This behavior is known as dynamic method dispatch. It allows code to be written in terms of the parent type while still producing child-specific behavior. This is a foundational idea behind many object-oriented designs.
How Inheritance Works at Runtime
At runtime, a child object is still one object, but it contains the inherited parent portion along with the
child-specific portion. When a Dog object is created from a class that extends
Animal, Java does not create two separate objects. It creates one object whose structure
includes the fields defined by Animal and the fields defined by Dog. This is why
inherited methods can operate on parent state and child methods can operate on child state within the same
object.
Method calls are resolved differently depending on whether the method is an instance method, a static method, or a field access. Overridden instance methods are resolved at runtime based on the actual object type. This means a parent reference can call a child implementation if the object is really a child object. Static methods, however, are not overridden in the same way. They are hidden and resolved using the reference type or class name. Fields are also resolved based on the reference type, not the runtime object type. These distinctions explain why Java interviews often include examples involving method overriding, method hiding, field hiding, upcasting, and downcasting.
Upcasting is usually safe because every child object is also a valid parent type. If Dog
extends Animal, assigning a Dog object to an Animal reference is
natural. Downcasting requires more care because not every Animal object is a Dog.
If the object was originally created as a Dog, a downcast back to Dog can work. If
the object was created only as an Animal, forcing it into a Dog reference causes a
runtime error. The instanceof operator is commonly used to check the actual runtime type before
downcasting.
Choosing Inheritance in Real Projects
In real projects, inheritance should be chosen for clarity, not just convenience. A good superclass captures common behavior that is stable across all subclasses. A good subclass adds or specializes behavior without violating the meaning of the superclass. If a subclass must disable inherited methods, ignore inherited fields, or constantly work around parent behavior, the hierarchy is probably wrong. This kind of design can lead to fragile code because changes in the parent class may unexpectedly break child classes.
A practical example is a payment system. A general Payment class may define common properties
such as amount, currency, and status. Specialized classes such as CardPayment and
UpiPayment may extend it if they are genuinely payment types and share common lifecycle
behavior. But a PaymentGateway should not extend Payment, because a gateway is not
a payment. It is a service that processes a payment. That relationship should be modeled with composition.
This distinction matters because object-oriented code is read by humans before it is executed by machines. A clear hierarchy helps new developers understand the domain quickly. A forced hierarchy creates questions: why does this class inherit this method, why is this field here, and why does this subclass override almost everything? Good inheritance reduces those questions by making the relationship obvious.
Inheritance vs Composition (Design Insight)
Inheritance is powerful, but it is not always the best design choice. Composition means one class contains
another class as a field and delegates work to it. Inheritance represents an IS-A relationship, while
composition represents a HAS-A relationship. For example, a Car is a Vehicle, so
inheritance may be reasonable. A Car has an Engine, so composition is more natural.
| Aspect | Inheritance | Composition |
|---|---|---|
| Relationship | IS-A | HAS-A |
| Coupling | Tight | Loose |
| Flexibility | Less | More |
Inheritance creates tighter coupling because a subclass depends on the structure and behavior of its superclass. Changes in the superclass can affect all subclasses. Composition is usually more flexible because one object can use another object without becoming a subtype of it. This is why many experienced developers say to prefer composition unless there is a clear and stable IS-A relationship.
A practical way to decide is to ask whether the child class should be usable everywhere the parent class is expected. If the answer is yes, inheritance may be appropriate. If the answer is no, composition is usually safer. This question protects the design from unnatural hierarchies and avoids fragile code.
Inheritance Examples and Rules (Quick Reference)
1. Basic Single Inheritance
class A {
void show() {
System.out.println("Class A");
}
}
class B extends A {
public static void main(String[] args) {
B b = new B();
b.show();
}
}
Explanation: B inherits methods of A.
Output: Class A
2. Inheriting Variables
class A {
int x = 10;
}
class B extends A {
public static void main(String[] args) {
B b = new B();
System.out.println(b.x);
}
}
Explanation: Instance variables are inherited.
Output: 10
3. Method Overriding (Runtime Polymorphism)
class A {
void show() {
System.out.println("Parent");
}
}
class B extends A {
void show() {
System.out.println("Child");
}
public static void main(String[] args) {
A a = new B();
a.show();
}
}
Explanation: Method resolved at runtime.
Output: Child
4. Overriding Rules (Same Signature)
class A {
void test() {}
}
class B extends A {
// void test(int x) {} // Overloading, not overriding
}
Explanation: Signature must be same for overriding.
5. Using super to Access Parent Method
class A {
void show() {
System.out.println("A");
}
}
class B extends A {
void show() {
super.show();
System.out.println("B");
}
public static void main(String[] args) {
new B().show();
}
}
Explanation: super calls parent method.
Output:
A
B
6. Constructor Execution Order
class A {
A() {
System.out.println("A constructor");
}
}
class B extends A {
B() {
System.out.println("B constructor");
}
public static void main(String[] args) {
new B();
}
}
Explanation: Parent constructor executes first.
Output:
A constructor
B constructor
7. Parameterized Parent Constructor
class A {
A(int x) {
System.out.println(x);
}
}
class B extends A {
B() {
super(10);
}
public static void main(String[] args) {
new B();
}
}
Explanation: Explicit super() required.
Output: 10
8. Multilevel Inheritance
class A {
void a() { System.out.println("A"); }
}
class B extends A {
void b() { System.out.println("B"); }
}
class C extends B {
public static void main(String[] args) {
C c = new C();
c.a();
c.b();
}
}
Explanation: Chain inheritance.
Output:
A
B
9. Hierarchical Inheritance
class A {
void show() {
System.out.println("A");
}
}
class B extends A {}
class C extends A {}
class Test {
public static void main(String[] args) {
new B().show();
new C().show();
}
}
Explanation: One parent, multiple children.
Output:
A
A
10. Multiple Inheritance with Classes
class A {}
class B {}
// class C extends A, B {} // Not allowed
Explanation: Java does not support multiple inheritance with classes.
11. Multiple Inheritance via Interfaces
interface A {
void a();
}
interface B {
void b();
}
class C implements A, B {
public void a() { System.out.println("A"); }
public void b() { System.out.println("B"); }
public static void main(String[] args) {
C c = new C();
c.a();
c.b();
}
}
Explanation: Achieved using interfaces.
Output:
A
B
12. Inheritance with final Method
class A {
final void show() {
System.out.println("Final Method");
}
}
class B extends A {
// void show() {} // Cannot override
}
Explanation: final methods cannot be overridden.
13. final Class Cannot Be Inherited
final class A {}
// class B extends A {} // Compile-time error
Explanation: final prevents inheritance.
14. Method Hiding (Static Methods)
class A {
static void show() {
System.out.println("A");
}
}
class B extends A {
static void show() {
System.out.println("B");
}
public static void main(String[] args) {
A.show();
B.show();
}
}
Explanation: Static methods are hidden, not overridden.
Output:
A
B
15. Field Hiding
class A {
int x = 10;
}
class B extends A {
int x = 20;
public static void main(String[] args) {
A a = new B();
System.out.println(a.x);
}
}
Explanation: Variables resolved at compile time.
Output: 10
16. Upcasting (Implicit)
class A {
void show() {
System.out.println("A");
}
}
class B extends A {}
class Test {
public static void main(String[] args) {
A a = new B(); // upcasting
a.show();
}
}
Explanation: Child object referenced by parent.
Output: A
17. Downcasting (Explicit)
class A {}
class B extends A {
void test() {
System.out.println("B method");
}
}
class Test {
public static void main(String[] args) {
A a = new B();
B b = (B) a;
b.test();
}
}
Explanation: Explicit cast required.
Output: B method
18. Invalid Downcasting
class A {}
class B extends A {}
class Test {
public static void main(String[] args) {
A a = new A();
// B b = (B) a; // Runtime ClassCastException
}
}
Explanation: Object must actually be child type.
19. instanceof with Inheritance
class A {}
class B extends A {}
class Test {
public static void main(String[] args) {
A a = new B();
System.out.println(a instanceof B);
}
}
Explanation: Runtime type check.
Output: true
20. Interview Summary – Inheritance
class A {
void show() {
System.out.println("A");
}
}
class B extends A {
void show() {
System.out.println("B");
}
public static void main(String[] args) {
A obj = new B();
obj.show();
}
}
Explanation: IS-A relationship, method overriding, runtime polymorphism.
Output: B
Common Beginner Mistakes
A common beginner mistake is using inheritance whenever two classes share some code. Shared code alone is
not enough reason to create a parent-child relationship. If the child class is not truly a specialized form
of the parent class, the design becomes misleading. For example, a ReportPrinter should not
extend Report just because both are related to reporting. A printer uses a report; it is not a
report. Composition would communicate that relationship more clearly.
Another frequent mistake is trying to access private parent members directly from the child class. Private fields belong to the parent implementation and are intentionally hidden. The correct approach is to expose controlled access through methods when access is needed. This preserves encapsulation and prevents child classes from depending on internal details that may change later.
Constructor behavior also causes confusion. New developers sometimes think constructors are inherited like
methods. They are not. A child constructor must ensure that an appropriate parent constructor is called.
When the parent has a no-argument constructor, Java can call it implicitly. When the parent has only
parameterized constructors, the child must call one explicitly with super(arguments).
Overusing inheritance is another design problem. Deep inheritance trees may look organized at first, but they can make behavior difficult to trace. If a method result depends on logic spread across four or five parent levels, maintenance becomes slow and error-prone. In practical Java projects, shallow and meaningful hierarchies are usually easier to maintain than deep abstract structures.
Developers also confuse inheritance with interfaces. A class extends another class when it wants to inherit implementation and state. A class implements an interface when it promises to provide certain behavior. Interfaces are better for defining capability across unrelated classes. Inheritance is better for sharing a real base identity and common implementation.
Interview-Ready Answers
Short Answer
Inheritance allows one Java class to acquire accessible fields and methods from another class using the
extends keyword. It supports code reuse, represents IS-A relationships, and enables method
overriding and runtime polymorphism.
Detailed Answer
In Java, inheritance is achieved using the extends keyword, allowing a subclass to reuse
accessible members of a superclass and add specialized behavior. Java supports single inheritance,
multilevel inheritance, and hierarchical inheritance through classes, but it does not support multiple
inheritance using classes. Multiple inheritance of type is achieved through interfaces. Inheritance is
important because it reduces duplication, improves extensibility, and enables polymorphism through method
overriding. However, it should be used only when a true IS-A relationship exists, because inheritance creates
tighter coupling between parent and child classes.
A strong interview answer should also mention constructor execution and access rules. Constructors are not inherited, but the parent constructor always executes before the child constructor. Public, protected, and same-package default members may be accessible depending on visibility, while private members cannot be accessed directly from the subclass. If the child class overrides a parent method, Java resolves the method call at runtime based on the actual object type. This runtime method resolution is the foundation of polymorphism in Java.
Key Takeaway
Inheritance is a powerful Java feature for building hierarchical relationships, reusing common behavior, and enabling polymorphism. It allows a subclass to extend a superclass and specialize behavior while keeping common logic in one place. At the same time, inheritance should be used with design discipline. The best use of inheritance is not simply to avoid duplicate code, but to express a clear and stable IS-A relationship. When that relationship is real, inheritance makes code easier to understand and extend. When it is forced, it creates tight coupling and design rigidity. A good Java developer knows both how inheritance works and when inheritance is the right choice.