Java Abstraction
Hide implementation details behind abstract types in Java using abstract classes and interfaces.
Abstraction is the fourth pillar of OOP: describe what something does without committing to how. You declare the operations a type supports, leave the implementation to the concrete classes, and write the rest of your program against the abstract type. This chapter is the conceptual overview — Java's two mechanisms for it, abstract class and interface, each get their own dedicated chapter.
The two questions
Every type declaration in Java answers two questions:
- What can callers do with values of this type? (its API)
- How is each of those operations implemented? (its body)
A concrete class answers both. An abstract type answers only the first and leaves the second to subtypes:
public interface Shape {
double area(); // what — every Shape has an area
}
public class Circle implements Shape {
double r;
public Circle(double r) { this.r = r; }
public double area() { return Math.PI * r * r; } // how
}
public class Square implements Shape {
double side;
public Square(double side) { this.side = side; }
public double area() { return side * side; }
}Shape says "every shape has an area." Circle and Square say how to compute one. Code that takes a Shape doesn't care which:
double sumAreas(List<Shape> shapes) {
double sum = 0;
for (Shape s : shapes) sum += s.area();
return sum;
}This function is closed over the abstraction. It works for Circle and Square today; for Triangle tomorrow; for Polygon six months from now. None of the new types require any change to sumAreas.
Java's two mechanisms
| Mechanism | What it provides | When to reach for it |
|---|---|---|
| abstract class | A partial class — some methods abstract, others with bodies, plus fields and constructors | When subtypes will share state and infrastructure code |
| interface | A pure (or near-pure) contract — methods that implementing classes must provide; no instance state | When subtypes only need to agree on a set of operations and may have nothing else in common |
A class extends one abstract class. A class can implement many interfaces. That asymmetry steers a lot of designs: if you find yourself wanting "multiple inheritance," interfaces are usually the answer.
Abstract classes — partial implementation
abstract on a class means "you can't instantiate this directly — only subclasses." abstract on a method means "no body here; every concrete subclass must provide one":
public abstract class Shape {
public abstract double area(); // every Shape must define this
// a concrete method, shared across all shapes
public final String describe() {
return getClass().getSimpleName() + " area=" + area();
}
}new Shape() is a compile error. new Circle() works. Inside describe, the call area() dispatches to the actual subclass's implementation — same polymorphism mechanism as any overridden method.
Use an abstract class when subtypes really do share code. If you find yourself writing the same helper in three subclasses, that's a signal to lift it into the parent.
Interfaces — the contract
An interface declares operations and leaves the implementation entirely to whoever implements it:
public interface Comparable<T> {
int compareTo(T other);
}
public class Money implements Comparable<Money> {
private final long cents;
public int compareTo(Money other) {
return Long.compare(this.cents, other.cents);
}
}Now Money works anywhere a Comparable is expected — Collections.sort(...), TreeMap, Arrays.sort(...), your own generic algorithms. The standard library and your code agree on Comparable as a shared abstraction; neither side knows about the other.
The vast majority of Java's standard interfaces (List, Map, Iterable, Runnable, Function, Comparator, AutoCloseable) work this way: a small, focused contract that many concrete classes plug into.
Abstraction as a design lever
The mechanical part of abstraction — the abstract keyword, the interface declaration — is small. The hard part is choosing which abstractions to define. Three patterns that show up over and over:
- Strategy. Define an interface for "the algorithm." Different implementations swap out the algorithm without changing the code that uses it.
Comparatoris the classic. - Template method. An abstract class implements the overall flow, with abstract methods at the variation points. Subclasses fill in the specific steps.
HttpServlet'sservicemethod is a famous example. - Plugin / extension point. A library publishes an interface; user code implements it; the library calls back into it. Servlet APIs, JDBC drivers, Spring's
BeanPostProcessor.
In every case, the win is the same: code that depends on the abstraction is closed against changes to the implementations, and open to additional implementations being added later.
Encapsulation vs abstraction
These two are close cousins and often get confused.
- Encapsulation hides the implementation of one specific class (private fields, controlled methods). It's a class-internal concern.
- Abstraction hides which class you're talking to behind a shared contract. It's a class-external concern.
A class with private fields and a tidy public API is encapsulated, but it's not yet abstracted — callers still depend on that specific class. Replace the type at the API boundary with an interface, and callers depend on the contract instead. Now you can swap implementations.
To see them working together, look at the encapsulation chapter: encapsulation locks down a single class, abstraction lets callers ignore which class they hold.
Common mistakes
A few traps catch newcomers to abstraction:
- Trying to instantiate an abstract type.
new Shape()is a compile error whenShapeisabstractor an interface. You instantiate a concrete subtype (new Circle(2)) and assign it to the abstract reference. - Abstracting too early. An interface with exactly one implementation, written "in case we need another later," is usually dead weight. Add the abstraction when the second implementation actually shows up, or when you genuinely need to decouple two modules. Premature abstraction adds indirection without buying flexibility.
- Leaking the concrete type. Declaring a field or parameter as
ArrayListinstead ofList, or returningHashMapinstead ofMap, ties callers to that specific class and undoes the abstraction. Prefer the most abstract type that still expresses what you need. - Confusing "no body" with "does nothing." An abstract method has no body because subclasses must supply one. A concrete method with an empty body is a real method that does nothing — a very different contract.
A worked example
Run the program below. It exercises both mechanisms: an abstract Shape class with shared describe code, and a pure Greeter interface. The expected output is:
Circle area=12.566370614359172
Square area=9.0
total = 21.57
Dear Alice,
hey Alice!Notice that totalArea and the greeter loop never name Circle, Square, FormalGreeter, or CasualGreeter — they speak only to the abstractions Shape and Greeter.
What's next
The next chapter is the concrete mechanics of abstract classes — abstract methods, what they let a subclass inherit, when to pick them over interfaces.