Hello! Welcome to the second module of our course. In the previous module, we established the core principles of OOP, culminating in a deep dive into polymorphism. You saw how upcasting allows you to treat specialized objects like EmailService and SMSService through a general NotificationService reference, enabling flexible and powerful code.
However, you might have noticed a conceptual gap in our NotificationService example. We could create an instance of NotificationService itself (new NotificationService()), but what does it mean to send a "generic" notification without a specific delivery method? It's a bit too abstract to be a concrete object.
This lesson introduces the perfect solution to this problem: abstract classes. We'll explore how to define a class that serves as a blueprint for its children—a class that cannot be instantiated on its own but can provide a mix of shared functionality and required behaviors. This is a fundamental concept for building robust and scalable backend systems.
What is an Abstract Class and Why Use It?
An abstract class is a restricted class that cannot be used to create objects. Its purpose is to serve as a base for subclasses, providing a common structure and potentially some shared implementation. Think of it as a template that other, more specific classes will fill in.
The primary motivations for using an abstract class are:
- To prevent instantiation of generic concepts: You can't create an object of a class that is too abstract to exist on its own (like a generic "Animal" or "Shape").
- To enforce a contract on subclasses: You can define methods that every subclass must implement, ensuring they all share a common interface.
- To share common code: You can still write regular, implemented methods that all subclasses inherit, avoiding code duplication.
This short video from "Coding with John" provides an excellent, clear introduction to the "what" and "why" of abstract classes.
Abstract Classes and Methods in Java Explained in 7 Minutes
This video explains the core concept of an abstract class and its primary use case in just a few minutes.
First, watch the segment on what an abstract class is. Note the abstract keyword and the fact that it cannot be instantiated. Next, watch the explanation for why they are useful. The Animal example is a classic and effective way to understand the motivation.
As the video illustrates, by declaring the Animal class abstract, you ensure that no one can create a generic Animal object. Instead, they are forced to create specific, concrete instances like Cat or Dog, which makes logical sense and leads to more robust code.
The Two Flavors of Methods: Abstract and Concrete
The real power of an abstract class comes from its ability to contain two types of methods:
- Abstract Methods: These are method declarations without a body (no curly braces
{}). They are marked with theabstractkeyword and act as a contract. Any concrete (non-abstract) class that extends the abstract class must provide an implementation for these methods. - Concrete Methods: These are regular methods with a full implementation. They are inherited by all subclasses and provide shared functionality, promoting code reuse.
Let's continue with the "Coding with John" video to see this distinction in action.
Abstract Classes and Methods in Java Explained in 7 Minutes
This part of the video clearly demonstrates how abstract and concrete methods coexist within an abstract class.
Watch the segment on abstract methods. Pay close attention to how makeNoise() is declared without a body and how the Cat class is forced to implement it. Then, see how concrete methods like printName() are also included, providing functionality that all subclasses can use directly.
This hybrid nature is what makes abstract classes so useful. You can define a common structure and enforce required behaviors with abstract methods, while simultaneously providing shared, reusable code with concrete methods.
The following diagram illustrates this perfectly with a Shape class.

Syntax and a Code Walkthrough
Now let's ground these concepts in actual Java code. The syntax is straightforward: you use the abstract keyword for both the class and its abstract methods. A critical rule to remember is: if a class contains even one abstract method, the class itself must be declared abstract.
The following article from GeeksforGeeks provides a concise summary and a clear, complete code example.
This article presents the formal definition, syntax, and a working example of an abstract class in Java.
Please read the following sections to see how these concepts are translated into code: Begin with the introduction to understand the definition and its purpose. Study the full code example. Observe the Shape class with its constructor, abstract area() method, and concrete getColor() method. See how the Circle subclass extends Shape and implements area(). Review the syntax summary for a quick reference. Finally, read the crucial rule under "Observation 8": if a class has an abstract method, it must be abstract.
From this example, notice a few key things:
- The
Shapeclass has a constructor. Even though you can't instantiateShapedirectly, its constructor is called viasuper()when you create an instance of a subclass (Circle). This is used to initialize common fields likecolor. - The
mainmethod creates aCircleobject but stores it in aShapereference:Shape s = new Circle(...). This is upcasting, which you learned about in the previous lesson. It works seamlessly with abstract classes. - You can then call both the concrete method (
s.getColor()) and the overridden abstract method (s.area()) on this reference, demonstrating polymorphism.
A Practical Case Study: Building a Workout Logger
To see how abstract classes are applied in a larger, more practical context, let's consider the design of a workout logging application. This scenario is perfect for an abstract class because different types of exercises (barbell, dumbbell, timed) share common properties but need to be displayed differently.
This article from Smashing Magazine walks through this exact problem.
This article provides a real-world case study, showing how an abstract class can be used to model different types of exercises in an application.
First, read the section that sets up the problem, from "Workout Log Case Study". This explains the need for a flexible design. Next, examine the abstract Exercise class. Notice how it stores common data (fields) and contains both a concrete method (getFormattedOutput) and an abstract one (getSetData). Finally, look at how the concrete subclasses are implemented, from BarbellExercise to BodyweightExercise. See how each one provides its own specific implementation for getSetData.
The design in this article is a great example of the Template Method pattern. The concrete getFormattedOutput() method in the Exercise abstract class defines the overall skeleton of an algorithm (print name, then print set data). However, it delegates the specific details of printing the set data to its subclasses by calling the abstract getSetData() method. This allows you to enforce a consistent overall structure while giving subclasses the flexibility to fill in the variable parts.
Your Turn: Refactoring the Notification System
Let's apply what you've learned by refactoring the NotificationService from our previous exercises. It's a perfect candidate for an abstract class.
Task:
-
Make
NotificationServiceAbstract:- Modify the
NotificationServiceclass declaration to bepublic abstract class NotificationService. - Make the
send(String message)method abstract:public abstract void send(String message);. Remove its implementation body. The logic here is that each notification type has its own unique way of being "sent," so there's no sensible default implementation.
- Modify the
-
Add a Concrete Method:
- Add a new, non-abstract (concrete) method to the
NotificationServiceclass calledlogStatus. This method will represent shared functionality.
public void logStatus(String status) { // In a real app, this would use a proper logging framework System.out.println("[" + java.time.LocalTime.now() + "] STATUS: " + status); } - Add a new, non-abstract (concrete) method to the
-
Update Subclasses:
- Modify
EmailServiceandSMSServiceto extend the abstractNotificationService. - Your IDE will now show an error because they don't implement the abstract
sendmethod. - Use the
@Overrideannotation and provide a concrete implementation for thesend(String message)method in bothEmailServiceandSMSService. The implementation should print a message specific to the service (e.g.,"[EMAIL] Sent: ...","[SMS] Delivered: ...").
- Modify
-
Test Your Implementation:
- In your
Mainclass, try to create an instance ofNotificationService:NotificationService ns = new NotificationService();. Observe that this now results in a compile-time error. This is the abstract class restriction in action! - Create an array of
NotificationServiceand populate it with instances ofEmailServiceandSMSService(this uses upcasting). - Loop through the array. For each
notificationin the array:- Call
notification.send("Your order confirmation."); - Call
notification.logStatus("PROCESSED");
- Call
- Run your
Mainclass and observe the polymorphic behavior.
- In your
This exercise solidifies your understanding by having you transform an existing class hierarchy to use an abstract class, directly demonstrating the benefits of enforcing a contract while sharing code.
Conclusion
In this lesson, we took a significant step towards creating more structured and robust object-oriented designs. You've learned how to define class blueprints that blend contractual obligations with reusable code.
Key Takeaways:
- An abstract class is declared with the
abstractkeyword and cannot be instantiated. - It serves as a template for subclasses.
- It can contain abstract methods (declarations without a body) which must be implemented by any concrete subclass.
- It can also contain concrete methods (with implementation) which are inherited by subclasses, promoting code reuse.
- Abstract classes are ideal for modeling concepts that are too generic to exist on their own and for sharing common code and structure among related classes.
In our next lesson, we will explore interfaces. While abstract classes are about sharing a common identity and implementation (is-a relationship), interfaces are purely about defining a capability (can-do relationship). We'll look at situations where unrelated classes need to share a common behavior, a problem that abstract classes can't solve as elegantly.