Sitemap

Java OOP Inheritance & Polymorphism

6 min readOct 3, 2026

--

One Family, Many Behaviors

Story: Our application now has Employees. But not every employee behaves the same way. A Developer codes, a Manager manages, and a Designer designs. We need to reuse common behavior while allowing specialized behavior.

Press enter or click to view image in full size

1. The Problem

Suppose we have:

Employee
├── Developer
├── Manager
└── Designer

Every employee has:

name
salary
employeeId
work()

But each employee also has specialized behavior.

We don’t want to duplicate common code.

We want to express:

Developer IS-A Employee
Manager IS-A Employee
Designer IS-A Employee

This leads to inheritance.

2. Inheritance

Java uses:

extends

Example:

class Employee {
String name;
double salary;
void work() {
System.out.println("Employee working");
}
}
class Developer extends Employee {
void writeCode() {
System.out.println("Writing code");
}
}

Now:

Developer developer = new Developer();

developer.name = "Amit";
developer.salary = 2400000;
developer.work();
developer.writeCode();

3. Parent and Child

Terminology:

Employee
├── Parent
├── Superclass
└── Base class

Developer
├── Child
├── Subclass
└── Derived class

All are commonly used terms.

4. The IS-A Rule

Inheritance should normally represent an IS-A relationship.

Developer IS-A Employee
Manager IS-A Employee
Dog IS-A Animal

But:

Car IS-A Engine

doesn’t make sense.

A car:

HAS-A Engine

This is composition.

Remember

IS-A
↓
Inheritance
HAS-A
↓
Composition

5. Parent Constructor Runs First

Consider:

class Employee {
Employee() {
System.out.println("Employee constructor");
}
}
class Developer extends Employee {
Developer() {
System.out.println("Developer constructor");
}
}

Now:

Developer d = new Developer();

Output:

Employee constructor
Developer constructor

The parent part is initialized before the child constructor completes.

6. super()

Java conceptually inserts:

Developer() {
super();
System.out.println("Developer constructor");
}

super() calls the parent's no-argument constructor.

7. Parameterized Parent Constructor

If the parent only has:

class Employee {
Employee(String name) {
System.out.println(name);
}
}

then the child must explicitly call it:

class Developer extends Employee {
Developer(String name) {
super(name);
}
}

Remember:

If a parent doesn’t have an accessible no-argument constructor, the child must explicitly invoke an appropriate parent constructor.

8. super(...) Rule

Just like this(...), super(...) must be the first statement in a constructor.

Correct:

Developer(String name) {
super(name);
}

Incorrect:

Developer(String name) {
System.out.println("Starting");
super(name);
}

9. super Can Access Parent Behavior

Suppose:

class Employee {
void work() {
System.out.println("Employee working");
}
}

Child:

class Developer extends Employee {
@Override
void work() {
super.work();
System.out.println("Developer coding");
}
}

super.work() means:

Call the parent implementation of work().

10. this vs super

this
↓
Current object/current class context
super
↓
Parent-class context

Constructor:

this(...)
↓
another constructor in same class
super(...)
↓
constructor in parent class

11. Access and Inheritance

Suppose:

class Employee {
public String name;
private double salary;
protected int employeeId;
}

A subclass can directly access:

name
employeeId

but cannot directly access:

salary

because salary is private.

Important

Inheritance does not override access control.

private still means private to the declaring class.

12. Protected

protected provides access:

  • Within the same package
  • To subclasses, subject to Java’s protected-access rules

For everyday understanding:

private
→ declaring class

protected
→ same package + subclass access

We can revisit exact edge cases later.

13. Method Overriding

Parent:

class Employee {
void work() {
System.out.println("Employee working");
}
}

Child:

class Developer extends Employee {
@Override
void work() {
System.out.println("Developer coding");
}
}

The child has overridden the parent’s method.

14. @Override

Use:

@Override

Whenever you intend to override a parent method.

It tells the compiler:

“I expect this method to override something from the parent.”

If the method signature is wrong, the compiler can catch the mistake.

15. Overloading vs Overriding

Overloading

void work()
void work(int hours)

Same method name, different parameters.

Usually resolved at compile time.

Overriding

class Employee {
void work() {}
}

class Developer extends Employee {
@Override
void work() {}
}

Child provides a new implementation of inherited behavior.

This participates in runtime polymorphism.

16. The Most Important Line

Now we write:

Employee employee = new Developer();

This contains two different types.

Employee
↓
Reference / compile-time type

Developer
↓
Actual object / runtime type

This distinction is the foundation of polymorphism.

17. What Does the Reference Type Control?

Suppose:

class Employee {
void work() {
}
}
class Developer extends Employee {
void writeCode() {
}
}

Now:

Employee employee = new Developer();

This works:

employee.work();

because Employee has work().

But:

employee.writeCode();

does not compile.

Why?

Because the reference type is:

Employee

and Employee does not declare writeCode().

18. The Golden Rule of Polymorphism

Reference type determines what members you can access.

Actual object type determines which overridden instance method runs.

Example:

Employee employee = new Developer();
employee.work();

Compile time:

Does Employee have work()?
YES

Runtime:

What is the actual object?
Developer

Does Developer override work()?
YES
Developer.work() runs

19. Runtime Polymorphism

This is called runtime polymorphism.

Get Amit Kumar’s stories in your inbox

Join Medium for free to get updates from this writer.

It is based on:

Inheritance
+
Method overriding
+
Parent reference
+
Child object

Example:

Employee e1 = new Developer();
Employee e2 = new Manager();

e1.work();
e2.work();

Possible output:

Developer coding
Manager managing

The same reference type can represent different actual objects.

20. Dynamic Method Dispatch

The process can be visualized as:

employee.work()
↓
Compile time
↓
Does Employee have work()?
↓
YES
↓
Runtime
↓
What object is employee pointing to?
↓
Developer
↓
Does Developer override work()?
↓
YES
↓
Developer.work()

This is dynamic method dispatch.

21. Why Polymorphism Is Useful

Suppose:

void makeEmployeeWork(Employee employee) {
employee.work();
}

We can call:

makeEmployeeWork(new Developer());
makeEmployeeWork(new Manager());
makeEmployeeWork(new Designer());

We don’t need separate methods for every employee type.

One abstraction handles many implementations.

22. Upcasting

This is valid:

Developer developer = new Developer();
Employee employee = developer;

This is called upcasting.

Developer
↓
Employee

Child → Parent.

No explicit cast is normally required.

Why is it safe?

Because:

Developer IS-A Employee

23. Downcasting

The reverse:

Employee employee = new Developer();

Developer developer =
(Developer) employee;

is called downcasting.

Parent reference → Child type.

An explicit cast is required.

24. Downcasting Can Fail

This compiles:

Employee employee = new Manager();

Developer developer =
(Developer) employee;

But at runtime:

ClassCastException

Why?

Because the actual object is:

Manager

not:

Developer

25. instanceof

You can check before downcasting:

if (employee instanceof Developer) {
Developer developer =
(Developer) employee;
}

Modern Java also supports:

if (employee instanceof Developer developer) {
developer.writeCode();
}

26. Polymorphism with Arrays

This is a beautiful example.

Employee[] employees = {
new Developer(),
new Manager(),
new Designer()
};

Then:

for (Employee employee : employees) {
employee.work();
}

Each object executes its own implementation.

Developer → Developer.work()
Manager → Manager.work()
Designer → Designer.work()

One array.

One reference type.

Multiple behaviors.

27. Real-World Example: Payments

Imagine:

Payment
├── UPI
├── CreditCard
└── Wallet

Each has:

pay()

Then:

Payment payment = new UPIPayment();
payment.pay();

The caller doesn’t need to know how UPI works.

It only knows:

Payment
↓
pay()

This is polymorphism combined with abstraction.

28. Important Exceptions

Not everything behaves like overridden instance methods.

private

Private methods do not participate in overriding.

final

A final method cannot be overridden.

static

Static methods are hidden, not dynamically overridden.

Fields

Fields are not dynamically dispatched like instance methods.

Example:

class Parent {
String name = "Parent";
}

class Child extends Parent {
String name = "Child";
}
Parent p = new Child();
System.out.println(p.name);

This prints:

Parent

Fields don’t use runtime method dispatch.

29. Single Class Inheritance

Java does not allow:

class C extends A, B {
}

A class can directly extend only one class.

But it can implement multiple interfaces:

class C implements A, B {
}

This becomes important when we study interfaces.

30. Composition vs Inheritance

Inheritance:

Developer IS-A Employee

Composition:

Car HAS-A Engine

Example:

class Engine {
void start() {
}
}

class Car {
private Engine engine;
void startCar() {
engine.start();
}
}

Composition often provides more flexibility and lower coupling.

This leads to the principle:

Favor composition over inheritance when the relationship is not a genuine, stable IS-A relationship.

31. The Big Picture

Employee
│
├── Developer
├── Manager
└── Designer

Common behavior:

name
salary
work()

Specialized behavior:

Developer → writeCode()
Manager → manageTeam()
Designer → design()

Polymorphism lets us write:

Employee employee = new Developer();

and use:

employee.work();

while Java executes:

Developer.work()

Quick Revision

Inheritance

A mechanism for creating specialized types from a parent type.

extends

Used for class inheritance.

super()

Calls a parent constructor.

super.method()

Calls the parent implementation.

Overriding

Child provides a new implementation of inherited behavior.

Overloading

Same method name with different parameter lists.

Upcasting

Child → Parent.

Downcasting

Parent reference → Child type.

Polymorphism

One abstraction/reference can represent multiple concrete implementations.

Golden Rule

Reference type controls what you can access. Actual object type controls which overridden instance method runs.

Composition

HAS-A relationship.

Inheritance

IS-A relationship.

--

--

Amit Kumar
Amit Kumar

Written by Amit Kumar

Software Engineer-1 @Esko | Ex- Bizongo, Samsung, Sirovate, ByteLearn, Bikayi | IIIT Gwalior'23