Parent Construction, `super`, and Inherited Access
Initialize the parent part of a child object with `super(...)`, predict constructor order, and understand `private`, `protected` and `final` members.
Vaatad tasuta eelvaate õppetundi.
Lesson Overview
When a child object is created, it is not only the child class's state that is initialized. Its parent portion must also be initialized into a valid state. Consider:
VideoLesson videoLesson = new VideoLesson(1L, "Inheritance Basics",
"https://cdn.tarkev.com/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, and Java uses super(...) for this purpose. This lesson follows a child object through construction and then looks at what the child may and may not touch in its parent: private state, protected members, and final methods and 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(...)andsuper(...) - Understand access rules for parent
privatemembers - Explain the basic purpose of
protected - Understand the effects of a
finalmethod and afinalclass
Parent State Must Be Initialized
Take the ContentItem and VideoLesson from the previous lesson. The parent's constructor validates and assigns its private final fields id and title; before we write its constructor, the child only adds its own fields:
public class VideoLesson extends ContentItem {
private final String videoUrl;
private final int durationInMinutes;
}
A VideoLesson object also contains the parent-defined state. Therefore, the parent constructor must execute.
Calling the Parent Constructor
To call the parent constructor from a child constructor, use super(...):
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 the parent constructor ContentItem(long id, String title).
Why Is super(...) Necessary?
The parent fields id and title are declared private final in ContentItem, and the child cannot directly assign them. This is invalid:
public VideoLesson(long id, String title) {
this.id = id;
this.title = title;
}
because id and title are private fields of the parent. javac rejects it with error: id has private access in ContentItem (and the same for title). The correct approach is super(id, title);: the parent initializes its own state.
Parent Validation Is Reused
The parent constructor already validates id and 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:
Diagrammi joonistamine…
When new VideoLesson(...) is executed, construction proceeds from parent toward child: the ContentItem constructor finishes before the rest of the VideoLesson constructor runs.
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:
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:
error: call to super must be first statement in constructor
Correct:
public VideoLesson(long id, String title) {
super(id, title);
System.out.println("Creating video");
}
A constructor-chaining call must be the first statement. Java 25 relaxed this rule (JEP 513, flexible constructor bodies): statements that do not use the object being built may come before super(...). This course targets Java 21, where the call must come first.
Automatic super()
If a child constructor does not explicitly write super(...) (or this(...)), the compiler inserts a call to super();. Example parent:
public class ContentItem {
public ContentItem() {
}
}
Child:
public class VideoLesson extends ContentItem {
public VideoLesson() {
}
}
The compiler treats the child constructor as if it were written like this:
public VideoLesson() {
super();
}
When Automatic super() Fails
Parent:
public class ContentItem {
public ContentItem(long id, String title) {
}
}
There is no ContentItem() constructor. Child:
public class VideoLesson extends ContentItem {
public VideoLesson() {
}
}
The compiler tries super();, but no matching parent constructor exists. The result is a compile error. javac reports:
error: constructor ContentItem in class ContentItem cannot be applied to given types;
required: long,String
found: no arguments
reason: actual and formal argument lists differ in length
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 to add an empty parent constructor just so that the child can compile:
public ContentItem() {
}
If the parent requires a valid id and a valid title, 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 a VideoLesson(long id, String title) constructor. 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 and title. A video lesson additionally requires videoUrl and 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 that this(...) calls another constructor in the same class. super(...) calls a constructor in the parent class.
| Syntax | Calls |
|---|---|
this(...) | Same-class constructor |
super(...) | Parent-class constructor |
You Cannot Directly Use Both
A constructor can have only one first statement. Invalid (it does not compile):
public VideoLesson(long id, String title) {
this(id, title, "https://example.com", 1);
super(id, title);
}
javac reports error: call to super must be first statement in constructor. If a constructor uses this(...), the delegated constructor must eventually call super(...).
Valid Constructor Chaining
Inside VideoLesson, the shorter constructor delegates to the full one, and only the full one calls the parent:
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);
// validate and assign videoUrl and durationInMinutes as above
}
Flow:
Diagrammi joonistamine…
Parent private State
If the parent declares private final String title;, the child cannot directly write:
System.out.println(title);
because title is private to ContentItem. javac reports error: title has private access in ContentItem.
Access Parent State Through Methods
Parent:
public String getTitle() {
return title;
}
Child:
public void printTitle() {
System.out.println(getTitle());
}
This works, and parent state remains encapsulated.
Private State Still Belongs to the Parent Portion
A VideoLesson object contains the state initialized by ContentItem, but only ContentItem code can touch its private fields. The parent owns the field, exposes controlled behavior, and the child (or any caller) uses that behavior. When we call videoLesson.publish() or videoLesson.isPublished(), the inherited public methods read and change the parent's private published field; the child never needs direct field access.
The protected Modifier
A protected member is accessible to subclasses, and to every class in the same package. 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. When possible, prefer:
private fields
+
public/protected methods
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
A member with no modifier is package-private:
void validate() {
}
A subclass in another package cannot directly access it. protected is different: it includes package access, and subclasses in other packages can also use the member. 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 it:
@Override
public long getId() {
return 999L;
}
This is a compile error:
error: getId() in VideoLesson cannot override getId() in ContentItem
overridden method is final
Why Use a final Method?
Use final when a specific inherited behavior must remain unchanged. For example, making getId() final 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 {
}
javac reports error: cannot inherit from final CourseCode.
Why Make a Class final?
It is useful when inheritance does not make sense, when immutable value behavior must stay fixed, or when subclass specialization is undesirable. Our earlier CourseCode value object 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.tarkev.com/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.tarkev.com/videos/super
Construction Flow
For new VideoLesson(1L, "Parent Construction", "https://cdn.tarkev.com/videos/super", 15), the high-level flow is:
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
For this call, the parent constructor rejects the id because id <= 0:
new VideoLesson(-1L, "Parent Construction",
"https://example.com/video",
15);
The parent constructor throws an IllegalArgumentException, so the rest of the VideoLesson constructor never runs. No object is created, and the caller does not receive a VideoLesson.
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. A useful rule:
Constructors should keep initialization predictable.
Important Terms
super(...)
A call to a parent constructor.
Automatic super()
If there is no explicit parent-constructor call, the compiler inserts a call to 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.
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?
Answer
Parent
Child
The parent constructor executes before the child constructor body.
Question 2
Parent:
public Parent(int value) {
}
Child:
public Child() {
}
Will it compile if the parent has no no-argument constructor?
Answer
No. The compiler tries to call super();, but no matching parent constructor exists.
Question 3
public class Parent {
private int value;
}
Can the child directly do value = 10;?
Answer
No. The field is private to the parent class.
Question 4
public final class CourseCode {
}
Can another class extend it?
Answer
No. A final class cannot be extended.
Question 5
public final long getId() {
return id;
}
Can a child override getId()?
Answer
No. A final method cannot be overridden.
Common Mistakes
Adding an Empty Parent Constructor to Make the Child Compile
When a child constructor does not compile because ContentItem has no no-argument constructor, the tempting fix is to add public ContentItem() {}. That lets content exist without an id or title. The fix belongs in the child: call super(id, title) with real values (see "Do Not Weaken Parent Invariants").
Making Fields protected Just for Convenience
A protected String title; lets any subclass write title = ""; and skip the parent's validation. Keep the field private and give subclasses methods instead.
Assuming Constructors Are Inherited
If VideoLesson declares only the four-argument constructor, new VideoLesson(1L, "Intro") does not compile, even though ContentItem(long, String) exists. Each class declares the constructors it offers.
Putting a Statement Before super(...)
Logging or checking something before super(id, title) fails in Java 21 with error: call to super must be first statement in constructor. Move the statement after the call.
Practice Exercise 1: Add QuizLesson
Solutions: lesson 4.10.
Create:
public class QuizLesson
extends ContentItem
with the fields questionCount and passingScore. Rules:
questionCount > 0passingScoreis between0and100- Use
super(id, title)to initialize parent state
Practice Exercise 2: Predict Constructor Order
Hierarchy:
Diagrammi joonistamine…
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 these two constructors:
VideoLesson(long id, String title, String videoUrl)
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:
CourseCodeclassContentItem.getId()ContentItem.publish()VideoLessonclass
Explain briefly.
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
- Creating a child object also initializes its parent state:
super(...)calls the parent constructor, which runs (and validates) before the rest of the child constructor. super(...)must be the first statement; without it the compiler insertssuper(), which fails if the parent has no no-argument constructor. Do not add an empty one that weakens the parent's rules.- Constructors are not inherited;
this(...)chains within a class and must eventually reachsuper(...). - If parent validation fails, the child object is never created.
- A child cannot touch the parent's
privatefields; it uses the parent's methods, so each class keeps owning and validating its own state. protectedgives subclasses deliberate access; preferprotectedmethods overprotectedfields.- A
finalmethod cannot be overridden and afinalclass cannot be extended; use them where specialization should be prohibited.
Mõtle enne
TulekulKontrollküsimused, mille puhul valid vastuse enne, kui õppetund selle näitab.