Introduction to Inheritance
Learn when inheritance fits, create a hierarchy with `extends`, add specialized state, and tell compile-time from runtime types.
You are viewing a free preview lesson.
Lesson Overview
As an application grows, we may find classes that share common state and behavior. Suppose Tarkev has three types of lesson content: VideoLesson, ArticleLesson and QuizLesson. Each has common information (ID, title, published status), and 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 write that common part three times. But removing duplication is not enough reason on its own: inheritance is useful when the classes genuinely share a meaningful common type (a VideoLesson is a ContentItem). This lesson shows how to build such a hierarchy with extends, what a parent-type reference can and cannot do, and when composition is the better choice.
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, VideoLesson holds its own copy of the common state and behavior:
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() and 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. In Java, class inheritance is declared using the extends keyword:
public class ContentItem {
}
public class VideoLesson extends ContentItem {
}
Here, ContentItem is the parent (superclass) and VideoLesson is the child (subclass). The declaration means "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 publish(), getId(), getTitle() or isPublished() in its own class. Even so, this works:
VideoLesson lesson =
new VideoLesson(1L, "Introduction to Inheritance");
lesson.publish();
System.out.println(lesson.getTitle());
System.out.println(lesson.isPublished());
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:
Drawing diagram…
Later:
Drawing diagram…
The parent is the common type, and the children are specialized types.
is-a and has-a
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
"Enrollment has a Course" and "Course has Lessons" are has-a relationships. We already learned composition for them:
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.tarkev.com/video/1",
15);
Here, the reference type is ContentItem and the actual object is a VideoLesson. This is allowed because every VideoLesson is a ContentItem, so wherever a ContentItem is expected, a VideoLesson can be supplied.
That is why one method can handle multiple child types:
public static void printTitle(ContentItem content) {
System.out.println(content.getTitle());
}
printTitle(videoLesson);
printTitle(articleLesson);
Both calls work because the caller depends only on the common parent contract, not on the specific child type.
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.
For example:
public static boolean publish(ContentItem content) {
return content.publish();
}
Both calls are valid, because both are publishable ContentItem objects:
publish(videoLesson);
publish(articleLesson);
Compile-Time Type vs. Runtime Type
Look again at ContentItem content = new VideoLesson(...) from the previous section. There are two types to notice:
Compile-time type
→ ContentItem
Runtime type
→ VideoLesson
Compile-Time Type Controls Available Members
This works, because those methods are part of the ContentItem contract:
content.getTitle();
content.publish();
But this will not compile:
content.getVideoUrl();
javac reports:
error: cannot find symbol
symbol: method getVideoUrl()
location: variable content of type ContentItem
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 we declare the variable as VideoLesson videoLesson = new VideoLesson(...) instead, both videoLesson.getTitle() and videoLesson.getVideoUrl() are available. The child exposes the parent API plus its child-specific API.
Upcasting
Assigning a child object or reference to a parent type is commonly called upcasting:
VideoLesson videoLesson = new VideoLesson(1L, "Inheritance",
"https://example.com/video",
10);
ContentItem content = videoLesson;
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, composition usually matches the relationship better:
Course has a pricing policy
Course has lessons
Enrollment has a learner
For 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. VideoLesson extends ContentItem is fine, but Java does not support multiple parent classes, so this declaration does not compile:
public class VideoLesson extends ContentItem, MediaResource
javac stops at the comma with error: '{' expected. We will learn about interfaces later.
Every Class Ultimately Extends Object
If a class has no explicit parent:
public class Learner {
}
Java makes it extend java.lang.Object automatically. Every class is part of the Object hierarchy. Familiar methods such as equals(), hashCode(), toString() and getClass() come from Object; we used some of them in the previous module.
Keep Hierarchies Simple
Technically, a hierarchy can be deeper:
Drawing diagram…
But unnecessary levels increase complexity. Avoid designing:
Drawing diagram…
unless every level has a clear meaning. A useful rule:
Start with the smallest meaningful hierarchy.
When Inheritance Is Appropriate
Consider inheritance when:
- A valid
is-arelationship exists. For example,VideoLesson is a ContentItem. - The parent concept is meaningful.
ContentItemis a real abstraction in the domain. - The child can correctly use the parent contract. If every
ContentItemcan publish, child types should meaningfully support that behavior. - The child adds specialization. For example,
VideoLessonadds a video URL and a duration.
When Inheritance Is Probably Wrong
Inheritance is probably wrong when:
- The relationship is
has-a.Enrollment has a Course: use composition. - The only reason is code reuse.
Course extends StringHelperis the wrong abstraction. - The child does not support parent behavior. If the parent offers
publish()but a child must always reject or disable that operation, the hierarchy may be wrong. - The hierarchy exists only for configuration differences.
FreeCourse,PaidCourseandDiscountedCoursemay not need three subclasses. ACoursewith price-related state or a collaborator may be simpler.
Complete Example
We use ContentItem from "Creating the Parent Class", VideoLesson from "Child-Specific State" and ArticleLesson from "Another Child Type", unchanged. Only Main is new.
Main.java
public class Main {
public static void main(String[] args) {
VideoLesson videoLesson =
new VideoLesson(1L, "Introduction to Inheritance",
"https://cdn.tarkev.com/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();
}
}
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.
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.
Predict the Result
Question 1
VideoLesson video = new VideoLesson(1L, "Inheritance",
"https://example.com/video",
10);
System.out.println(video.getTitle());
Will it compile?
Answer
Yes. getTitle() is inherited from the parent ContentItem.
Question 2
ContentItem content = new VideoLesson(1L, "Inheritance",
"https://example.com/video",
10);
System.out.println(content.getVideoUrl());
Will it compile?
Answer
No. The reference's compile-time type is ContentItem, and getVideoUrl() is not part of the parent contract.
Question 3
ContentItem first = new ArticleLesson(1L, "Objects", "Content", 5);
ContentItem second = first;
first.publish();
System.out.println(second.isPublished());
What is the output?
Answer
true
first and second refer to the same object.
Common Mistakes
Using Inheritance As Soon As You See Duplicate Code
Duplication can be an initial signal, but check the type relationship first: two classes that both validate a title are not necessarily the same kind of thing.
Treating has-a as is-a
Wrong:
Enrollment extends Course
Correct:
Enrollment has a Course
Calling a Child-Specific Method Through a Parent Reference
content.getVideoUrl() does not compile when content is declared as ContentItem, even if the object is a VideoLesson (see "Compile-Time Type Controls Available Members"). Declare the variable with the child type when you need the child's API.
Using Inheritance Only for Utility Reuse
Course extends ValidationUtils gives Course the helper methods, but also makes every Course "a ValidationUtils" that can be passed wherever one is expected. Use composition or call the helper instead.
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.
Practice Exercise 1: Create QuizLesson
Solutions: lesson 4.10.
Create:
public class QuizLesson
extends ContentItem
with the additional fields questionCount and passingScore. Rules:
questionCount > 0passingScoreis between0and100- Parent state must be initialized using
super(...)
Practice Exercise 2: is-a or has-a?
Classify each pair as is-a or has-a:
VideoLessonandContentItemCourseandLessonInstructorandUserEnrollmentandCourseQuizLessonandContentItem
Practice Exercise 3: Parent Reference
ContentItem content =
new ArticleLesson(1L, "Objects", "Article content", 5);
Which of these compile? Explain.
content.getTitle();
content.publish();
content.getContent();
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 a VideoLesson, an ArticleLesson and a QuizLesson.
Practice Exercise 5: Inheritance or Composition?
Choose inheritance or composition for each pair, and explain each using is-a or has-a:
- Course + Lesson
- VideoLesson + ContentItem
- Enrollment + Learner
- Instructor + User
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
- Inheritance (
extends) creates a hierarchy of related types: the parent (superclass) holds common state and behavior, and each child (subclass) inherits it and adds its own. - Use inheritance only for a genuine
is-arelationship (VideoLesson is a ContentItem);has-arelationships (Enrollment has a Course) use composition. - Code reuse alone is not a reason to inherit: a child must meaningfully satisfy the parent's contract wherever the parent is expected.
- A parent-type reference can hold a child object (upcasting, no cast needed).
- The compile-time type decides which methods you can call; the runtime type is the object actually created.
- A Java class extends exactly one class, and every class is ultimately part of the
Objecthierarchy. - Keep hierarchies small: every level must have a clear meaning.
Think first
Coming soonCheckpoint questions that ask you to commit to an answer before the lesson shows it.