Inheritance, Interfaces, and Polymorphism

Parent Construction, `super`, and Inherited Access

ReadingPreview

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

Lesson Overview

When a child object is created, only the child class's state is not initialized.

Its parent portion must also be initialized into a valid state.

Consider:

VideoLesson videoLesson =
        new VideoLesson(
                1L,
                "Inheritance Basics",
                "https://cdn.liveklass.io/videos/inheritance",
                18
        );

A VideoLesson is a ContentItem.

Conceptually, the object's state is:

ContentItem state
→ id
→ title
→ published

VideoLesson state
→ videoUrl
→ durationInMinutes

Therefore, it is not enough for the VideoLesson constructor to initialize only the video-specific fields.

The parent state must also be initialized.

Java uses:

super(...)

for this purpose.

In this lesson, we will learn:

  • Why the parent constructor executes
  • super(...)
  • Automatic super()
  • Constructor execution order
  • Reusing parent validation
  • this(...) vs. super(...)
  • Parent private state
  • protected access
  • final methods
  • final classes

Learning Objectives

After completing this lesson, you will be able to:

  • Explain why the parent constructor is required during child-object creation
  • Call a parent constructor using super(...)
  • Understand when automatic super() works
  • Predict constructor execution order
  • Distinguish between this(...) and super(...)
  • Understand access rules for parent private members
  • Explain the basic purpose of protected
  • Understand the effects of a final method and a final class

Parent State Must Be Initialized

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;
    }
}

Child:

public class VideoLesson
        extends ContentItem {

    private final String videoUrl;
    private final int durationInMinutes;
}

A VideoLesson object also contains parent-defined state.

Therefore, the parent constructor must execute.

Calling the Parent Constructor

To call the parent constructor from a child constructor, use:

super(...)

Example:

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
        );

        this.videoUrl =
                videoUrl;

        this.durationInMinutes =
                durationInMinutes;
    }
}

Here:

super(
        id,
        title
);

calls:

ContentItem(
        long id,
        String title
)

Why Is super(...) Necessary?

Parent fields:

private final long id;
private final String title;

The child cannot directly assign them.

Invalid:

public VideoLesson(
        long id,
        String title
) {
    this.id =
            id;

    this.title =
            title;
}

because id and title are private fields of the parent.

Correct approach:

super(
        id,
        title
);

The parent initializes its own state.

Parent Validation Is Reused

The parent constructor already validates:

id
title

So the child constructor does not need to repeat the same validation.

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

    // validate video-specific state
}

Responsibility:

ContentItem
→ validates parent state

VideoLesson
→ validates child-specific state

This reduces duplication and centralizes the rules.

Constructor Execution Order

Suppose the hierarchy is:

Object
└── ContentItem
    └── VideoLesson

When:

new VideoLesson(...);

is executed, construction proceeds from parent toward child.

Simplified:

ContentItem constructor
↓
VideoLesson constructor

Observing the Order

public class ContentItem {

    public ContentItem(
            long id,
            String title
    ) {
        System.out.println(
                "ContentItem constructor"
        );
    }
}
public class VideoLesson
        extends ContentItem {

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

        System.out.println(
                "VideoLesson constructor"
        );
    }
}

Usage:

new VideoLesson(
        1L,
        "Inheritance"
);

Output:

ContentItem constructor
VideoLesson constructor

Parent Initializes First

It is important that parent state is initialized first.

After:

super(
        id,
        title
);

the child constructor can safely use parent methods.

Example:

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

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

By this point, the parent constructor has already initialized title.

super(...) Must Be First

Wrong:

public VideoLesson(
        long id,
        String title
) {
    System.out.println(
            "Creating video"
    );

    super(
            id,
            title
    );
}

This will not compile.

Correct:

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

    System.out.println(
            "Creating video"
    );
}

A constructor-chaining call must be the first statement.

Automatic super()

If a child constructor does not explicitly write super(...), the compiler tries to call:

super();

Example parent:

public class ContentItem {

    public ContentItem() {
    }
}

Child:

public class VideoLesson
        extends ContentItem {

    public VideoLesson() {
    }
}

Conceptually, the child constructor behaves like:

public VideoLesson() {
    super();
}

When Automatic super() Fails

Parent:

public class ContentItem {

    public ContentItem(
            long id,
            String title
    ) {
    }
}

There is no:

ContentItem()

Child:

public class VideoLesson
        extends ContentItem {

    public VideoLesson() {
    }
}

The compiler tries:

super();

but no matching parent constructor exists.

Result:

Compile error

The child must explicitly call a valid parent constructor:

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

Do Not Weaken Parent Invariants

A poor fix would be:

public ContentItem() {
}

just so that the child can compile.

If the parent requires:

valid id
valid title

then an empty constructor could allow invalid parent state.

Prefer:

super(
        id,
        title
);

and keep the parent's rules intact.

Constructors Are Not Inherited

The parent has:

public ContentItem(
        long id,
        String title
) {
}

That does not automatically give the child:

VideoLesson(
        long id,
        String title
)

The child must declare its own constructor:

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

Methods can be inherited.

Constructors are not inherited.

Child Can Require Additional State

The parent requires:

id
title

A video lesson additionally requires:

videoUrl
durationInMinutes

Complete constructor:

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;
}

Initialization responsibility remains clear.

this(...) vs. super(...)

We previously learned:

this(...)

calls another constructor in the same class.

super(...)

calls a constructor in the parent class.

Summary:

SyntaxCalls
this(...)Same-class constructor
super(...)Parent-class constructor

You Cannot Directly Use Both

A constructor can have only one first statement.

Invalid:

public VideoLesson(
        long id,
        String title
) {
    this(
            id,
            title,
            "https://example.com",
            1
    );

    super(
            id,
            title
    );
}

If a constructor uses:

this(...)

the delegated constructor must eventually call:

super(...)

Valid Constructor Chaining

public class VideoLesson
        extends ContentItem {

    private final String videoUrl;
    private final int durationInMinutes;

    public VideoLesson(
            long id,
            String title,
            String videoUrl
    ) {
        this(
                id,
                title,
                videoUrl,
                1
        );
    }

    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(
                    "Duration must be positive."
            );
        }

        this.videoUrl =
                videoUrl.strip();

        this.durationInMinutes =
                durationInMinutes;
    }
}

Flow:

3-argument VideoLesson constructor
↓
4-argument VideoLesson constructor
↓
ContentItem constructor

Parent private State

Parent:

private final String title;

The child cannot directly write:

System.out.println(
        title
);

because title is private to ContentItem.

Access Parent State Through Methods

Parent:

public String getTitle() {
    return title;
}

Child:

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

This works.

Parent state remains encapsulated.

Private State Still Belongs to the Parent Portion

A VideoLesson object contains the state initialized by ContentItem.

But child code cannot directly access the parent's private fields.

Think:

Parent owns the field
↓
Parent exposes controlled behavior
↓
Child uses that behavior

Example:

videoLesson.publish();

The parent method can modify its own private:

published

field.

The child does not need direct field access.

Inherited Public Methods

If the parent declares:

public boolean isPublished() {
    return published;
}

the child can use:

videoLesson.isPublished();

Public parent methods become available through child objects.

The protected Modifier

protected can give subclasses access to a member.

Example:

protected String contentTypeLabel() {
    return "Content";
}

Child:

public class VideoLesson
        extends ContentItem {

    public void printType() {
        System.out.println(
                contentTypeLabel()
        );
    }
}

This can be useful when the parent deliberately exposes behavior for subclasses.

Avoid protected Fields by Default

Technically:

protected String title;

allows direct field access from the child.

Then the child may write:

title =
        "";

and bypass parent validation.

This weakens encapsulation.

Prefer:

private fields
+
public/protected methods

when possible.

protected Should Be Deliberate

When you mark something:

protected

you are effectively saying:

Subclasses are allowed to depend on this member.

So use it intentionally—not simply because child code needs convenient access.

Package-Private vs. protected

No modifier:

boolean validate() {
}

means package-private.

A subclass in another package cannot directly access it.

protected is different because it can participate in subclass access across packages.

The exact cross-package rules become more relevant in larger inheritance hierarchies.

For now:

package-private
→ package collaboration

protected
→ subclass-oriented access

final Method

A parent method marked final cannot be overridden by child classes.

public final long getId() {
    return id;
}

The child cannot redefine:

@Override
public long getId() {
    return 999L;
}

Compile error.

Why Use a final Method?

Use final when a specific inherited behavior must remain unchanged.

Example:

public final long getId() {
    return id;
}

might preserve a stable identity contract.

Do not mechanically make every method final.

Use it where preventing specialization is intentional.

final Class

A final class cannot be extended.

public final class CourseCode {

}

Invalid:

public class SpecialCourseCode
        extends CourseCode {

}

Why Make a Class final?

It can be useful when:

  • Inheritance does not make sense
  • Immutable value behavior should stay fixed
  • Subclass specialization is undesirable

Our earlier:

CourseCode

is a good example.

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 final long getId() {
        return id;
    }

    public final String getTitle() {
        return title;
    }

    public final boolean isPublished() {
        return published;
    }

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

        published =
                true;

        return true;
    }
}

VideoLesson.java

public class VideoLesson
        extends ContentItem {

    private final String videoUrl;
    private final int durationInMinutes;

    public VideoLesson(
            long id,
            String title,
            String videoUrl
    ) {
        this(
                id,
                title,
                videoUrl,
                1
        );
    }

    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;
    }
}

Main.java

public class Main {

    public static void main(
            String[] args
    ) {
        VideoLesson videoLesson =
                new VideoLesson(
                        1L,
                        "Parent Construction",
                        "https://cdn.liveklass.io/videos/super",
                        15
                );

        videoLesson.publish();

        System.out.println(
                videoLesson.getId()
        );

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

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

        System.out.println(
                videoLesson.getVideoUrl()
        );
    }
}

Output:

1
Parent Construction
true
https://cdn.liveklass.io/videos/super

Construction Flow

For:

new VideoLesson(
        1L,
        "Parent Construction",
        "https://cdn.liveklass.io/videos/super",
        15
);

High-level flow:

1. VideoLesson constructor selected
2. super(id, title) called
3. ContentItem validates id
4. ContentItem validates title
5. ContentItem initializes parent state
6. Control returns to VideoLesson constructor
7. VideoLesson validates videoUrl
8. VideoLesson validates duration
9. VideoLesson initializes child state
10. Object creation completes

Parent Validation Failure Stops Creation

If:

new VideoLesson(
        -1L,
        "Parent Construction",
        "https://example.com/video",
        15
);

the parent constructor rejects:

id <= 0

The child initialization does not complete.

The caller does not receive a valid VideoLesson object.

Constructor Safety

During construction, avoid depending on child-specific behavior before child state has been initialized.

The parent constructor should primarily initialize and validate its own state.

Detailed overriding behavior comes in the next lesson.

Useful rule:

Constructors should keep initialization predictable.

Common Mistakes

Forgetting the Parent Constructor Requirement

Parent:

public ContentItem(
        long id,
        String title
) {
}

Child:

public VideoLesson() {
}

will not compile unless a matching parent constructor is called.

Weak No-Argument Parent Constructor

Do not add:

public ContentItem() {
}

just to avoid passing required parent state.

super(...) After Another Statement

Invalid.

It must be the first constructor statement.

Accessing a Parent private Field Directly

Invalid:

System.out.println(
        title
);

if title privately belongs to the parent.

Making Fields protected Just for Convenience

This can expose parent invariants to uncontrolled child mutation.

Assuming Constructors Are Inherited

They are not.

Calling Both this(...) and super(...) Directly

Both cannot appear as separate constructor-chain calls in the same constructor.

Making Every Method final

Use final only when specialization should intentionally be prohibited.

Important Terms

super(...)

A call to a parent constructor.

Automatic super()

If there is no explicit parent-constructor call, the compiler attempts to call the no-argument parent constructor.

Parent State

Superclass-defined state that is initialized as part of the child object.

Inherited Member

A member made available to the child through the parent.

protected

A subclass-oriented access level.

final Method

A method that cannot be overridden.

final Class

A class that cannot be extended.

Practice Exercise 1: Add QuizLesson

Create:

public class QuizLesson
        extends ContentItem

Fields:

questionCount
passingScore

Rules:

  • questionCount > 0
  • passingScore is between 0 and 100
  • Use super(id, title) to initialize parent state

Practice Exercise 2: Predict Constructor Order

Hierarchy:

ContentItem
↓
VideoLesson
↓
LiveVideoLesson

Each constructor prints one line.

What will the output order be when:

new LiveVideoLesson(...)

is executed?

Practice Exercise 3: Fix the Constructor

Parent:

public ContentItem(
        long id,
        String title
) {
}

Child:

public VideoLesson(
        String videoUrl
) {
    this.videoUrl =
            videoUrl;
}

Fix the child constructor so that it includes the required parent state.

Practice Exercise 4: Replace a protected Field

Weak parent:

protected boolean published;

Refactor so that:

  • The field is private
  • The parent exposes isPublished()
  • The parent exposes controlled publish()

Practice Exercise 5: this(...) and super(...)

Create:

VideoLesson(
        long id,
        String title,
        String videoUrl
)

and:

VideoLesson(
        long id,
        String title,
        String videoUrl,
        int durationInMinutes
)

The shorter constructor should delegate using:

this(...)

Practice Exercise 6: Choose final

Decide whether final is reasonable for:

  1. CourseCode class
  2. ContentItem.getId()
  3. ContentItem.publish()
  4. VideoLesson class

Explain briefly.

Predict the Result

Question 1

public class Parent {

    public Parent() {
        System.out.println(
                "Parent"
        );
    }
}
public class Child
        extends Parent {

    public Child() {
        System.out.println(
                "Child"
        );
    }
}

Then:

new Child();

What prints?

Question 2

Parent:

public Parent(
        int value
) {
}

Child:

public Child() {
}

Will it compile if the parent has no no-argument constructor?

Question 3

public class Parent {

    private int value;
}

Can the child directly do:

value =
        10;

Question 4

public final class CourseCode {

}

Can another class extend it?

Question 5

public final long getId() {
    return id;
}

Can a child override getId()?

Predict the Result Answers

Answer 1

Parent
Child

The parent constructor executes before the child constructor body.

Answer 2

No.

The compiler tries to call:

super();

but no matching parent constructor exists.

Answer 3

No.

The field is private to the parent class.

Answer 4

No.

A final class cannot be extended.

Answer 5

No.

A final method cannot be overridden.

Knowledge Check

Question 1

Why does the parent constructor execute when a child object is created?

Question 2

What does super(...) do?

Question 3

Where must super(...) appear?

Question 4

What does the compiler try if there is no explicit parent-constructor call?

Question 5

When does automatic super() fail?

Question 6

Are constructors inherited?

Question 7

What is the difference between this(...) and super(...)?

Question 8

Can one constructor contain two direct constructor-chaining calls?

Question 9

Can a child directly access a parent's private field?

Question 10

How can a child access parent state?

Question 11

Why can a protected field be risky?

Question 12

Why can a protected member be useful?

Question 13

What does a final method prevent?

Question 14

What does a final class prevent?

Question 15

What happens to child construction if parent validation fails?

Knowledge Check Answers

Answer 1

To initialize the child object's parent-defined state into a valid state.

Answer 2

It calls a matching constructor in the parent class.

Answer 3

As the first statement of the constructor.

Answer 4

It tries to call the no-argument:

super();

Answer 5

When the parent has no accessible no-argument constructor.

Answer 6

No.

The child declares its own constructors.

Answer 7

this(...) calls another constructor in the same class.

super(...) calls a parent constructor.

Answer 8

No.

Only one constructor-chaining call can be the first statement.

Answer 9

No.

Answer 10

By using the parent's accessible public or protected methods.

Answer 11

The child can bypass parent validation or invariants through direct state mutation.

Answer 12

When the parent intentionally wants to allow subclass access or customization.

Answer 13

Method overriding.

Answer 14

Subclass creation.

Answer 15

Object creation fails and child initialization does not complete.

Lesson Summary

In this lesson, we learned:

  • Parent state is also initialized when a child object is created
  • super(...) calls a parent constructor
  • Child classes can reuse parent validation
  • Parent initialization completes before child initialization
  • super(...) must be the first constructor statement
  • If there is no explicit call, the compiler attempts to call super()
  • If the parent has no no-argument constructor, a matching super(...) must be called explicitly
  • An empty constructor should not be added if it weakens parent invariants
  • Constructors are not inherited
  • A child can initialize its own additional required state
  • this(...) performs same-class constructor chaining
  • super(...) calls a parent constructor
  • A child cannot directly access parent private state
  • A child can use encapsulated parent state through parent methods
  • protected provides subclass-oriented access
  • protected fields can weaken parent encapsulation
  • A final method prevents overriding
  • A final class prevents inheritance
  • If parent validation fails, child construction does not complete
  • Parent and child each own validation responsibility for their own state