Abstraction
Abstraction means hiding the complicated implementation details of something and exposing only the essential, simple parts that a caller actually needs. When you drive a car you turn the key and press the pedals — you don't need to know how fuel injection or the transmission works internally. The dashboard and pedals are the abstraction; the engine is the hidden complexity behind it.
In Java, abstraction shows up constantly. When you call list.add(item) on a List, you don't know (or care) whether it's backed by an array or a linked structure internally — you just know the contract: give it an item, it gets added. That contract is the abstraction.
Abstraction vs. encapsulation
These two terms are among the most frequently confused in object-oriented programming, because in practice they often show up together in the same class. But they answer different questions.
Abstraction | Encapsulation | |
|---|---|---|
Question it answers | What does this thing do? | How is this thing's data protected? |
Hides | Implementation complexity, from the caller | Internal state, from outside code |
Goal | Present a simple, usable interface | Prevent invalid or unintended state changes |
Java mechanism | Abstract classes, interfaces | Access modifiers ( |
Analogy | A car's pedals and steering wheel (simple controls) | The locked engine bay (you can't reach in and yank a wire) |
Java's two abstraction mechanisms
Java gives you exactly two language-level tools for expressing abstraction: abstract classes and interfaces. Both let you define what a type can do without necessarily saying how — the “how” is filled in later by a concrete subclass or implementing class.
A peek at both mechanisms
// An abstract class: partial implementation + a contract
abstract class Shape {
abstract double area(); // no body — subclasses must supply one
void describe() { // concrete method, shared by all shapes
System.out.println("This shape has area " + area());
}
}
// An interface: a pure contract, describing capability
interface Drawable {
void draw(); // every implementer must provide this
}
class Circle extends Shape implements Drawable {
private final double radius;
Circle(double radius) {
this.radius = radius;
}
@Override
double area() {
return Math.PI * radius * radius;
}
@Override
public void draw() {
System.out.println("Drawing a circle of radius " + radius);
}
}Callers of Circle interact with it through the simple, abstract surface — area(), describe(), draw() — without ever needing to know that area() multiplies π by the radius squared internally. That's abstraction in action.
Choosing between them
Reach for an abstract class when related types share common state or behavior you want to implement once and inherit — see the dedicated Abstract Classes page.
Reach for an interface when you want to describe a capability that unrelated classes can all promise to support, including multiple capabilities on the same class — see the dedicated Interfaces page.
It is common, as shown above, for a single class to both extend an abstract class and implement one or more interfaces.