Inheritance, Interfaces, and Polymorphism

Introduction to Inheritance

ReadingPreview

আপনি একটি free preview lesson দেখছেন।

Lesson Overview

As an application grows, we may find classes that share common state and behavior.

Suppose LiveKlass has three types of lesson content:

VideoLesson
ArticleLesson
QuizLesson

Each has common information:

ID
Title
Published status

At the same time, each has specialized information.

VideoLesson
→ video URL
→ duration

ArticleLesson
→ article content
→ reading time

QuizLesson
→ question count
→ passing score

We do not want to unnecessarily duplicate common behavior.

However, using inheritance only for code reuse is also not a good idea.

Inheritance is useful when different classes genuinely share a meaningful common type.

Example:

VideoLesson is a ContentItem
ArticleLesson is a ContentItem
QuizLesson is a ContentItem

In this lesson, we will learn:

  • What inheritance is
  • Parent and child classes
  • extends
  • Inherited behavior
  • Child-specific state
  • Type hierarchy
  • is-a relationships
  • Parent references and child objects
  • Compile-time type vs. runtime type
  • When inheritance should be used
  • When composition is better

Learning Objectives

After completing this lesson, you will be able to:

  • Explain the purpose of inheritance
  • Identify parent and child classes
  • Create a simple hierarchy using extends
  • Use inherited methods
  • Add specialized state to a child class
  • Identify a valid is-a relationship
  • Assign a child object to a parent-type reference
  • Distinguish compile-time and runtime types
  • Understand the difference between code reuse and type relationships
  • Identify incorrect inheritance designs

The Duplication Problem

Without inheritance:

public class VideoLesson {

    private final long id;
    private final String title;

    private boolean published;

    public VideoLesson(
            long id,
            String title
    ) {
        if (id <= 0) {
            throw new IllegalArgumentException(
                    "Lesson ID must be positive."
            );
        }

        if (
                title == null
                || title.isBlank()
        ) {
            throw new IllegalArgumentException(
                    "Lesson title is required."
            );
        }

        this.id =
                id;

        this.title =
                title.strip();

        this.published =
                false;
    }

    public boolean publish() {
        if (published) {
            return false;
        }

        published =
                true;

        return true;
    }

    public String getTitle() {
        return title;
    }
}

If ArticleLesson also needs the same:

id
title
published
validation
publish()
getTitle()

we would need to write them again, creating duplication.

But duplication alone does not mean inheritance should be used.

First ask:

Are VideoLesson and ArticleLesson genuinely specialized forms of the same broader type?

Here, the answer is:

Yes

Both are learning content.

So a common parent can be meaningful.

What Is Inheritance?

Inheritance allows one class to inherit the accessible behavior and type contract of another class and create a specialized type.

Parent:

public class ContentItem {

}

Child:

public class VideoLesson
        extends ContentItem {

}

Here:

ContentItem
→ parent / superclass

VideoLesson
→ child / subclass

The extends Keyword

In Java, class inheritance is declared using:

extends
public class VideoLesson
        extends ContentItem {

}

Meaning:

VideoLesson is a ContentItem

Creating the Parent Class

Common state and behavior can be placed in the parent.

public class ContentItem {

    private final long id;
    private final String title;

    private boolean published;

    public ContentItem(
            long id,
            String title
    ) {
        if (id <= 0) {
            throw new IllegalArgumentException(
                    "Content ID must be positive."
            );
        }

        if (
                title == null
                || title.isBlank()
        ) {
            throw new IllegalArgumentException(
                    "Content title is required."
            );
        }

        this.id =
                id;

        this.title =
                title.strip();

        this.published =
                false;
    }

    public boolean publish() {
        if (published) {
            return false;
        }

        published =
                true;

        return true;
    }

    public long getId() {
        return id;
    }

    public String getTitle() {
        return title;
    }

    public boolean isPublished() {
        return published;
    }
}

ContentItem owns the common content behavior.

Creating a Child Class

public class VideoLesson
        extends ContentItem {

    public VideoLesson(
            long id,
            String title
    ) {
        super(
                id,
                title
        );
    }
}

super(...) calls the parent constructor.

We will learn the details in the next lesson.

For now, simply observe:

Even when a child object is created, the parent's required state must still be initialized.

Inherited Behavior

VideoLesson did not declare these methods in its own class:

publish()
getId()
getTitle()
isPublished()

Even so:

VideoLesson lesson =
        new VideoLesson(
                1L,
                "Introduction to Inheritance"
        );

lesson.publish();

System.out.println(
        lesson.getTitle()
);

System.out.println(
        lesson.isPublished()
);

works.

Output:

Introduction to Inheritance
true

The methods are inherited from ContentItem.

Child-Specific State

A child can inherit common behavior while also keeping its own specialized state.

public class VideoLesson
        extends ContentItem {

    private final String videoUrl;
    private final int durationInMinutes;

    public VideoLesson(
            long id,
            String title,
            String videoUrl,
            int durationInMinutes
    ) {
        super(
                id,
                title
        );

        if (
                videoUrl == null
                || videoUrl.isBlank()
        ) {
            throw new IllegalArgumentException(
                    "Video URL is required."
            );
        }

        if (durationInMinutes <= 0) {
            throw new IllegalArgumentException(
                    "Video duration must be positive."
            );
        }

        this.videoUrl =
                videoUrl.strip();

        this.durationInMinutes =
                durationInMinutes;
    }

    public String getVideoUrl() {
        return videoUrl;
    }

    public int getDurationInMinutes() {
        return durationInMinutes;
    }
}

Now VideoLesson has:

Inherited
→ getId()
→ getTitle()
→ publish()
→ isPublished()

Own
→ getVideoUrl()
→ getDurationInMinutes()

Another Child Type

public class ArticleLesson
        extends ContentItem {

    private final String content;
    private final int estimatedReadingMinutes;

    public ArticleLesson(
            long id,
            String title,
            String content,
            int estimatedReadingMinutes
    ) {
        super(
                id,
                title
        );

        if (
                content == null
                || content.isBlank()
        ) {
            throw new IllegalArgumentException(
                    "Article content is required."
            );
        }

        if (
                estimatedReadingMinutes
                <= 0
        ) {
            throw new IllegalArgumentException(
                    "Reading time must be positive."
            );
        }

        this.content =
                content.strip();

        this.estimatedReadingMinutes =
                estimatedReadingMinutes;
    }

    public String getContent() {
        return content;
    }

    public int getEstimatedReadingMinutes() {
        return estimatedReadingMinutes;
    }
}

Type Hierarchy

Now we have:

ContentItem
├── VideoLesson
└── ArticleLesson

Later:

ContentItem
├── VideoLesson
├── ArticleLesson
└── QuizLesson

Parent:

common type

Children:

specialized types

The is-a Relationship

The most useful question when evaluating inheritance is:

Is the child genuinely a type of the parent?

Valid:

VideoLesson is a ContentItem
ArticleLesson is a ContentItem
QuizLesson is a ContentItem

Invalid:

Enrollment is a Course
Course is a Learner
Course is a Lesson

Invalid relationships should not be modeled with inheritance.

has-a Is Different

Consider:

Enrollment has a Course
Course has Lessons

These are has-a relationships.

We already learned composition:

public class Enrollment {

    private final Course course;
}

Compare:

VideoLesson is a ContentItem
→ inheritance

Enrollment has a Course
→ composition

Parent Reference Can Hold a Child Object

Java allows:

ContentItem content =
        new VideoLesson(
                1L,
                "Java Inheritance",
                "https://cdn.liveklass.io/video/1",
                15
        );

Here:

Reference type
→ ContentItem

Actual object
→ VideoLesson

Why Is This Allowed?

Because:

Every VideoLesson is a ContentItem

So wherever a ContentItem is expected, a VideoLesson can be supplied.

Common Method for Different Child Types

public static void printTitle(
        ContentItem content
) {
    System.out.println(
            content.getTitle()
    );
}

Then:

printTitle(
        videoLesson
);

printTitle(
        articleLesson
);

One method can handle multiple child types.

That is because the caller depends on the common parent contract.

Substitutability — The Core Idea

An important design idea of inheritance is:

Wherever a parent type is expected, a child object should be usable there meaningfully.

Example:

public static boolean publish(
        ContentItem content
) {
    return content.publish();
}

Both are valid:

publish(
        videoLesson
);

publish(
        articleLesson
);

because both are publishable ContentItem objects.

Compile-Time Type vs. Runtime Type

Consider:

ContentItem content =
        new VideoLesson(
                1L,
                "Java Inheritance",
                "https://cdn.liveklass.io/video/1",
                15
        );

There are two types to notice:

Compile-time type
→ ContentItem

Runtime type
→ VideoLesson

Compile-Time Type Controls Available Members

This works:

content.getTitle();
content.publish();

because those methods are part of the ContentItem contract.

But:

content.getVideoUrl();

will not compile.

Why?

getVideoUrl() is only part of the:

VideoLesson

contract.

The reference variable's compile-time type is:

ContentItem

and that type does not know about this method.

Child Reference Exposes Child-Specific API

If:

VideoLesson videoLesson =
        new VideoLesson(
                1L,
                "Java Inheritance",
                "https://cdn.liveklass.io/video/1",
                15
        );

then:

videoLesson.getTitle();
videoLesson.getVideoUrl();

are both available.

The child exposes:

parent API
+
child-specific API

Upcasting

Assigning a child object or reference to a parent type:

VideoLesson videoLesson =
        new VideoLesson(
                1L,
                "Inheritance",
                "https://example.com/video",
                10
        );

ContentItem content =
        videoLesson;

is commonly called upcasting.

No explicit cast is required.

The conversion is naturally safe because:

Every VideoLesson is a ContentItem

Inheritance Is Not Just Code Reuse

Suppose:

public class Course
        extends ValidationUtils {

}

Maybe Course can reuse some validation methods.

But ask:

Is a Course a ValidationUtils?

No.

So the inheritance relationship is meaningless.

Code reuse alone is not a sufficient reason for inheritance.

Composition May Be Better

If a class uses another capability:

Course has a pricing policy
Course has lessons
Enrollment has a learner

composition usually matches the relationship better.

Example:

public class Course {

    private PricingPolicy pricingPolicy;
}

instead of:

public class Course
        extends PricingPolicy {
}

Java Supports Single Class Inheritance

A Java class can directly extend only one class.

Valid:

public class VideoLesson
        extends ContentItem {

}

Java does not support:

public class VideoLesson
        extends ContentItem,
                MediaResource {

}

for multiple parent classes.

We will learn about interfaces later.

Every Class Ultimately Extends Object

If:

public class Learner {

}

has no explicit parent, Java classes are conceptually still part of the Object hierarchy.

Familiar methods come from Object:

equals()
hashCode()
toString()
getClass()

some of which we used in the previous module.

Keep Hierarchies Simple

Technically, a hierarchy can be deeper:

ContentItem
└── VideoLesson
    └── LiveVideoLesson

But unnecessary levels increase complexity.

Avoid designing:

ContentItem
→ LearningContent
→ MediaContent
→ VideoContent
→ RecordedContent
→ RecordedVideoLesson

unless every level has a clear meaning.

Useful rule:

Start with the smallest meaningful hierarchy.

When Inheritance Is Appropriate

Consider inheritance when:

A valid is-a relationship exists

VideoLesson is a ContentItem

The parent concept is meaningful

ContentItem

is a real abstraction in the domain.

The child can correctly use the parent contract

If every ContentItem can publish, child types should meaningfully support that behavior.

The child adds specialization

Example:

VideoLesson
→ video URL
→ duration

When Inheritance Is Probably Wrong

The relationship is has-a

Enrollment has a Course

Use composition.

The only reason is code reuse

Course extends StringHelper

This is the wrong abstraction.

The child does not support parent behavior

If the parent says:

publish();

but a child must always reject or disable that operation, the hierarchy may be wrong.

The hierarchy exists only for configuration differences

Example:

FreeCourse
PaidCourse
DiscountedCourse

may not need three subclasses.

A Course with price-related state or a collaborator may be simpler.

Complete Example

ContentItem.java

public class ContentItem {

    private final long id;
    private final String title;

    private boolean published;

    public ContentItem(
            long id,
            String title
    ) {
        if (id <= 0) {
            throw new IllegalArgumentException(
                    "Content ID must be positive."
            );
        }

        if (
                title == null
                || title.isBlank()
        ) {
            throw new IllegalArgumentException(
                    "Content title is required."
            );
        }

        this.id =
                id;

        this.title =
                title.strip();

        this.published =
                false;
    }

    public boolean publish() {
        if (published) {
            return false;
        }

        published =
                true;

        return true;
    }

    public long getId() {
        return id;
    }

    public String getTitle() {
        return title;
    }

    public boolean isPublished() {
        return published;
    }
}

VideoLesson.java

public class VideoLesson
        extends ContentItem {

    private final String videoUrl;
    private final int durationInMinutes;

    public VideoLesson(
            long id,
            String title,
            String videoUrl,
            int durationInMinutes
    ) {
        super(
                id,
                title
        );

        if (
                videoUrl == null
                || videoUrl.isBlank()
        ) {
            throw new IllegalArgumentException(
                    "Video URL is required."
            );
        }

        if (durationInMinutes <= 0) {
            throw new IllegalArgumentException(
                    "Video duration must be positive."
            );
        }

        this.videoUrl =
                videoUrl.strip();

        this.durationInMinutes =
                durationInMinutes;
    }

    public String getVideoUrl() {
        return videoUrl;
    }

    public int getDurationInMinutes() {
        return durationInMinutes;
    }
}

ArticleLesson.java

public class ArticleLesson
        extends ContentItem {

    private final String content;
    private final int estimatedReadingMinutes;

    public ArticleLesson(
            long id,
            String title,
            String content,
            int estimatedReadingMinutes
    ) {
        super(
                id,
                title
        );

        if (
                content == null
                || content.isBlank()
        ) {
            throw new IllegalArgumentException(
                    "Article content is required."
            );
        }

        if (
                estimatedReadingMinutes
                <= 0
        ) {
            throw new IllegalArgumentException(
                    "Reading time must be positive."
            );
        }

        this.content =
                content.strip();

        this.estimatedReadingMinutes =
                estimatedReadingMinutes;
    }

    public String getContent() {
        return content;
    }

    public int getEstimatedReadingMinutes() {
        return estimatedReadingMinutes;
    }
}

Main.java

public class Main {

    public static void main(
            String[] args
    ) {
        VideoLesson videoLesson =
                new VideoLesson(
                        1L,
                        "Introduction to Inheritance",
                        "https://cdn.liveklass.io/videos/inheritance",
                        18
                );

        ArticleLesson articleLesson =
                new ArticleLesson(
                        2L,
                        "Understanding Type Hierarchies",
                        "A hierarchy models related types.",
                        7
                );

        videoLesson.publish();
        articleLesson.publish();

        printSummary(
                videoLesson
        );

        printSummary(
                articleLesson
        );
    }

    private static void printSummary(
            ContentItem content
    ) {
        System.out.println(
                "ID: "
                + content.getId()
        );

        System.out.println(
                "Title: "
                + content.getTitle()
        );

        System.out.println(
                "Published: "
                + content.isPublished()
        );

        System.out.println();
    }
}

Possible output:

ID: 1
Title: Introduction to Inheritance
Published: true

ID: 2
Title: Understanding Type Hierarchies
Published: true

One method handled different specialized objects through their common parent type.

Common Mistakes

Using Inheritance As Soon As You See Duplicate Code

Duplication can be an initial signal, but the type relationship must still be validated.

Treating has-a as is-a

Wrong:

Enrollment extends Course

Correct:

Enrollment has a Course

Calling a Child-Specific Method Through a Parent Reference

ContentItem content =
        new VideoLesson(
                1L,
                "Inheritance",
                "https://example.com/video",
                10
        );

Invalid:

content.getVideoUrl();

The method is not part of the compile-time type's contract.

Using Inheritance Only for Utility Reuse

Course extends ValidationUtils

This is not a meaningful domain type relationship.

Too Many Levels Too Early

Deep hierarchies make code harder to understand and change.

Making Parent Fields protected

Exposing parent internals for child convenience can weaken encapsulation.

We will study inherited access in more detail in the next lesson.

Important Terms

Inheritance

A mechanism where one class inherits another class's type contract and accessible behavior to create a specialized type.

Parent Class / Superclass

The class being extended.

Child Class / Subclass

The class that extends the parent.

extends

The keyword used to declare class inheritance.

Type Hierarchy

An organized relationship between parent and child types.

is-a

The child is a specialized type of the parent.

Upcasting

Assigning a child object to a parent-type reference.

Compile-Time Type

The declared type of a reference variable.

Runtime Type

The concrete type of the object that was actually created.

Practice Exercise 1: Create QuizLesson

Create:

public class QuizLesson
        extends ContentItem

Additional fields:

questionCount
passingScore

Rules:

  • questionCount > 0
  • passingScore is between 0 and 100
  • Parent state must be initialized using super(...)

Practice Exercise 2: is-a or has-a?

Classify:

  1. VideoLesson and ContentItem
  2. Course and Lesson
  3. Instructor and User
  4. Enrollment and Course
  5. QuizLesson and ContentItem

Choose:

is-a
has-a

Practice Exercise 3: Parent Reference

ContentItem content =
        new ArticleLesson(
                1L,
                "Objects",
                "Article content",
                5
        );

Which compile?

content.getTitle();

content.publish();

content.getContent();

Explain.

Practice Exercise 4: Common Behavior

Write:

static void publishAndPrint(
        ContentItem content
)

It should:

  • Publish the content
  • Print the title
  • Print publication status

Call it with:

VideoLesson
ArticleLesson
QuizLesson

Practice Exercise 5: Inheritance or Composition?

Choose:

  1. Course + Lesson
  2. VideoLesson + ContentItem
  3. Enrollment + Learner
  4. Instructor + User

Explain each using:

is-a
has-a

Predict the Result

Question 1

VideoLesson video =
        new VideoLesson(
                1L,
                "Inheritance",
                "https://example.com/video",
                10
        );

System.out.println(
        video.getTitle()
);

Will it compile?

Question 2

ContentItem content =
        new VideoLesson(
                1L,
                "Inheritance",
                "https://example.com/video",
                10
        );

System.out.println(
        content.getVideoUrl()
);

Will it compile?

Question 3

ContentItem first =
        new ArticleLesson(
                1L,
                "Objects",
                "Content",
                5
        );

ContentItem second =
        first;

first.publish();

System.out.println(
        second.isPublished()
);

What is the output?

Predict the Result Answers

Answer 1

Yes.

getTitle() is inherited from the parent ContentItem.

Answer 2

No.

The reference's compile-time type is ContentItem, and getVideoUrl() is not part of the parent contract.

Answer 3

true

first and second refer to the same object.

Knowledge Check

Question 1

What is inheritance?

Question 2

What are parent and child classes?

Question 3

What does extends do?

Question 4

What is an inherited method?

Question 5

What is an is-a relationship?

Question 6

Can a parent-type reference hold a child object?

Question 7

Does a parent reference automatically expose child-specific methods?

Question 8

What is compile-time type?

Question 9

What is runtime type?

Question 10

Is the main purpose of inheritance only code reuse?

Question 11

What is generally better for modeling a has-a relationship?

Question 12

How many parent classes can a Java class directly extend?

Question 13

Which class hierarchy is every Java class ultimately part of?

Question 14

Why is avoiding deep hierarchies useful?

Knowledge Check Answers

Answer 1

A mechanism where one class inherits another class's type contract and accessible behavior to create a specialized type.

Answer 2

The parent defines the common type. The child extends the parent and creates a specialized type.

Answer 3

It declares one class as a subclass of another class.

Answer 4

An accessible method declared in the parent class that can be used by a child object.

Answer 5

The child is genuinely a specialized type of the parent.

Answer 6

Yes.

ContentItem content =
        new VideoLesson(...);

Answer 7

No.

Available methods depend on the contract of the compile-time reference type.

Answer 8

The declared type of the reference variable.

Answer 9

The concrete type of the object that was actually created.

Answer 10

No.

A meaningful common type and substitutability are more important.

Answer 11

Composition.

Answer 12

One.

Answer 13

Object.

Answer 14

As inheritance levels increase, reasoning about behavior, initialization, and dependencies becomes harder.

Lesson Summary

In this lesson, we learned:

  • Inheritance creates a hierarchy of related types
  • A parent can define common state, behavior, and a common contract
  • A child inherits from a parent using extends
  • The parent is also called a superclass, and the child is also called a subclass
  • A child can use accessible methods inherited from the parent
  • A child can add its own specialized fields and methods
  • Valid inheritance requires a meaningful is-a relationship
  • VideoLesson is a ContentItem is valid inheritance
  • Enrollment has a Course is composition
  • A parent-type reference can hold a child object
  • Compile-time type determines the available API
  • Runtime type represents the actual object type
  • Assigning a child to a parent type is safe and commonly called upcasting
  • Inheritance is not merely a technique for removing duplicate code
  • A child should meaningfully satisfy the parent contract
  • Java supports single class inheritance
  • Java classes are ultimately part of the Object hierarchy
  • Deep inheritance hierarchies can become unnecessarily complex
  • Composition is generally better for has-a relationships
  • A good inheritance hierarchy is small, meaningful, and focused on types