1. The concept of abstraction
In short: abstraction is the art of seeing complex things simply.
In programming, abstraction is the process of identifying common characteristics and behavior of a group of objects while ignoring particular details (for example, how exactly something works “under the hood”). Imagine drawing a city map: it marks roads, buildings, rivers — but not details like the colour of each curtain in every window. A map is an abstraction of the city.
In OOP, abstraction means creating classes and interfaces that reflect only the properties and actions important for the task, hiding unnecessary details.
Real-world examples
- Transport is an abstraction. It doesn’t matter whether it’s a bus, a bicycle, or a spaceship — the important part is what all transport shares: it can move, and it has passengers and a driver.
- Animal is also an abstraction. All animals can breathe, eat, and move — how exactly they do so depends on the specific species.
- Payment is an abstraction in banking software. You don’t always care whether it’s by card, via PayPal, or in Bitcoin — the point is that you can process it and get a result.
Why is abstraction important?
- Think less about details that aren’t relevant to the current task.
- Design systems that are easier to extend and maintain.
- Work with objects through a common interface without worrying about the concrete implementation.
2. Abstraction in Java
In Java, abstraction is implemented with two primary tools:
- Abstract classes (abstract class)
- Interfaces (interface)
In this lecture we’ll focus on abstract classes. (We’ll get to interfaces very soon!)
An abstract class is a class that isn’t intended to be instantiated directly. It provides a common foundation (a template) for other classes. An abstract class can define both implemented methods (with a body) and abstract methods (without a body) — ones that must be implemented by subclasses.
An abstract method is a method without an implementation, i.e., without a body. It states: “All descendants of this class must implement this method in their own way.”
Example: the “Shape” abstraction
public abstract class Shape {
public abstract void draw(); // Abstract method — no body!
}
Here we’re saying: “All shapes can be drawn, but we don’t know exactly how — let each subclass decide for itself.”
Why don’t you always need to know implementation details?
When you use an abstraction, you work with an object through its public surface — the set of methods it must support. How a method works internally is not important.
For example, you call payment.process() on an object to process a payment. How it’s implemented doesn’t matter — as long as it works. This allows you to:
- Swap one implementation for another without rewriting the code that uses it.
- Simplify testing (you can substitute implementations with “stubs”).
- Make the code more flexible and resilient to change.
3. Benefits of abstraction
Simpler design and maintenance
Abstraction lets you ignore the irrelevant. You don’t need to know exactly how a car engine is built to drive it — a steering wheel, pedals, and the instruction “drive forward” are enough. The same applies to code: if you work with an abstract class or an interface, you see only what you need.
Ease of extending the system
When your system is built on abstractions, you can easily add new types of objects. For example, if you have an abstract class Shape, you can add a new shape — Triangle — without changing existing code.
Reduced coupling between components
If different parts of a program communicate only through abstractions, they can be changed independently. It’s like an outlet and a plug: if the standard matches, you can plug in any device.
4. Practical examples
Let’s see what abstraction looks like in practice. We’ll use examples you can plug into your learning app and play with.
Example 1: The “Shape” class (Figure)
public abstract class Shape {
public abstract void draw();
}
Now let’s create a few concrete shapes:
public class Circle extends Shape {
@Override
public void draw() {
System.out.println("Drawing a circle");
}
}
public class Rectangle extends Shape {
@Override
public void draw() {
System.out.println("Drawing a rectangle");
}
}
Use the abstraction:
Shape s1 = new Circle();
Shape s2 = new Rectangle();
s1.draw(); // Drawing a circle
s2.draw(); // Drawing a rectangle
Here we work with variables of type Shape — and it doesn’t matter which shape is inside. That’s the power of abstraction!
Example 2: The “Payment” class
public abstract class Payment {
public abstract void process();
}
Concrete implementations:
public class CreditCardPayment extends Payment {
@Override
public void process() {
System.out.println("Processing a credit card payment");
}
}
public class PaypalPayment extends Payment {
@Override
public void process() {
System.out.println("Processing a payment via PayPal");
}
}
Usage:
Payment[] payments = {
new CreditCardPayment(),
new PaypalPayment()
};
for (Payment p : payments) {
p.process();
}
As a result, each payment is processed in its own way, but the code that calls them doesn’t have to think about it.
5. Abstraction vs. implementation details: how not to get lost
Abstraction is the “what,” not the “how”
When you design an abstraction, you answer the question: “What should the object be able to do?”
Implementation details answer: “How does it do it?”
For example:
- Abstraction: “Any shape must be able to draw itself (draw()).”
- Implementation details: “A circle is drawn as a curve, a rectangle — with four lines.”
Analogy from everyday life
Think of a TV remote. You don’t care how it transmits a signal — what matters is that there are buttons to “power on,” “change channel,” and “turn up the volume.” That’s the abstraction — the set of buttons you can press. Engineers who design the remote have to think about the implementation details.
6. How to identify abstractions in a program
Step 1. Find common traits
Look at the objects in your domain. What do they have in common? For example, all vehicles can move, all animals can eat, all payments can be processed.
Step 2. Define an abstract class
Create an abstract class that contains only what’s common to all objects.
public abstract class Transport {
public abstract void move();
}
Step 3. Implement the details in subclasses
public class Car extends Transport {
@Override
public void move() {
System.out.println("The car drives on the road");
}
}
public class Bicycle extends Transport {
@Override
public void move() {
System.out.println("The bicycle moves by pedaling");
}
}
Step 4. Use the abstraction in code
Transport[] transports = {
new Car(),
new Bicycle()
};
for (Transport t : transports) {
t.move();
}
Your code works with abstractions — and doesn’t depend on implementation details.
7. Abstraction vs. detail: balance
A common beginner’s mistake — trying to make an abstraction too specific or, conversely, too generic.
- If an abstraction is too generic (for example, a class “Object” with a method “doSomething”), it brings no benefit.
- If an abstraction is too specific (for example, “A circle with a red outline and radius 5”), it loses its meaning — it’s simpler to write a concrete class right away.
Golden rule: an abstraction should capture only what is truly common and important for your task.
8. Common mistakes when working with abstraction
Error #1: trying to instantiate an abstract class.
Abstract classes aren’t meant to be instantiated directly. If you try to write Shape s = new Shape();, the compiler will report: Cannot instantiate the type Shape. It’s like trying to buy just “transport” in a store. You can’t: you need to choose a specific bicycle, car, or bus.
Error #2: forgetting to implement an abstract method in a subclass.
If a class extends an abstract class but doesn’t implement all of its abstract methods, it also becomes abstract — and cannot be instantiated. Make sure you’ve implemented all required methods.
Error #3: mixing abstraction and implementation details.
If you start adding to the abstract class details needed by only one subclass, that’s a sign of a poor abstraction. For example, if the abstract class Payment gets a field cardNumber, yet not all payments are card-based.
Error #4: overusing abstractions.
Don’t create an abstraction for its own sake. If your system has only one type of object and it will never be extended, abstraction will only complicate the code.
GO TO FULL VERSION