Inheritance, Interfaces, and Polymorphism
Introduction to Inheritance
You are viewing a 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-arelationships- 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-arelationship - 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
VideoLessonandArticleLessongenuinely 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 > 0passingScoreis between0and100- Parent state must be initialized using
super(...)
Practice Exercise 2: is-a or has-a?
Classify:
VideoLessonandContentItemCourseandLessonInstructorandUserEnrollmentandCourseQuizLessonandContentItem
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:
- Course + Lesson
- VideoLesson + ContentItem
- Enrollment + Learner
- 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-arelationship VideoLesson is a ContentItemis valid inheritanceEnrollment has a Courseis 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
Objecthierarchy - Deep inheritance hierarchies can become unnecessarily complex
- Composition is generally better for
has-arelationships - A good inheritance hierarchy is small, meaningful, and focused on types