- Understand the purpose and use of abstract classes
- Master interface declaration and implementation
- Learn when to use abstract classes vs interfaces
Abstract Classes and Interfaces in Java
As programs grow, classes share common characteristics. Dogs, cats, birds—all animals. Cars, motorcycles, bicycles—all vehicles. How do you model these relationships in a flexible, type-safe way?
Abstract classes and interfaces both let you define contracts that other classes follow. But they serve different purposes. Knowing when to use each is key.
The Need for Abstraction
Take a drawing app. You have shapes: circles, rectangles, triangles. Each needs to be drawn and calculate its area. Without abstraction:
class Circle {
void draw() { /* draw circle */ }
double area() { /* calculate circle area */ }
}
class Rectangle {
void draw() { /* draw rectangle */ }
double area() { /* calculate rectangle area */ }
}
But how do you store different shapes together? How do you ensure every new shape implements draw() and area()? Abstraction solves both problems.
Abstract Classes
An abstract class is a class that cannot be instantiated directly. It serves as a blueprint for other classes, potentially containing both implemented and unimplemented methods.
When to Use Abstract Classes
Use an abstract class when:
- You want to share code among closely related classes
- You expect subclasses to have common fields or methods
- You want to declare non-public members (protected, package-private)
- You need constructors or instance initialization
Declaring an Abstract Class
public abstract class Shape {
// Instance variables - shared by all shapes
protected String color;
protected boolean filled;
// Constructor - called by subclasses
public Shape(String color, boolean filled) {
this.color = color;
this.filled = filled;
}
// Abstract methods - MUST be implemented by subclasses
public abstract double area();
public abstract double perimeter();
// Concrete method - shared implementation
public void describe() {
System.out.println("A " + (filled ? "filled " : "") + color + " shape");
}
// Concrete method with common logic
public String getColor() {
return color;
}
}
Key observations:
- The class is marked
abstract- you cannot writenew Shape() - Abstract methods have no body - just a signature ending with
; - Concrete methods have full implementations that subclasses inherit
- Abstract classes can have constructors (called via
super())
Implementing an Abstract Class
Subclasses must implement all abstract methods (unless they're also abstract):
public class Circle extends Shape {
private double radius;
public Circle(String color, boolean filled, double radius) {
super(color, filled); // Call parent constructor
this.radius = radius;
}
@Override
public double area() {
return Math.PI * radius * radius;
}
@Override
public double perimeter() {
return 2 * Math.PI * radius;
}
// Can add additional methods
public double getRadius() {
return radius;
}
}
public class Rectangle extends Shape {
private double width;
private double height;
public Rectangle(String color, boolean filled, double width, double height) {
super(color, filled);
this.width = width;
this.height = height;
}
@Override
public double area() {
return width * height;
}
@Override
public double perimeter() {
return 2 * (width + height);
}
}
Using Abstract Classes Polymorphically
The power comes from treating different shapes uniformly:
// Can store any Shape subclass
Shape[] shapes = {
new Circle("red", true, 5.0),
new Rectangle("blue", false, 4.0, 3.0),
new Circle("green", true, 2.5)
};
// Process them uniformly
double totalArea = 0;
for (Shape shape : shapes) {
shape.describe(); // Inherited method
totalArea += shape.area(); // Polymorphic call
}
System.out.println("Total area: " + totalArea);
Interfaces
An interface is a completely abstract type that defines a contract. It specifies what a class can do, without dictating how it does it.
When to Use Interfaces
Use an interface when:
- You want unrelated classes to share a capability
- You need multiple inheritance of type
- You want to define a contract without implementation
- You're designing for flexibility and loose coupling
Declaring an Interface
public interface Drawable {
// All methods are implicitly public and abstract
void draw();
// Constants are implicitly public, static, and final
int DEFAULT_LINE_WIDTH = 1;
}
Before Java 8, interfaces could only contain abstract methods. Now they can also have:
public interface Drawable {
// Abstract method (must be implemented)
void draw();
// Default method (has implementation, can be overridden)
default void drawWithBorder() {
System.out.println("Drawing border...");
draw(); // Calls the abstract method
}
// Static method (belongs to the interface itself)
static Drawable empty() {
return () -> { }; // Do-nothing drawable
}
// Private method (Java 9+, helper for default methods)
private void logDrawing() {
System.out.println("Drawing operation performed");
}
}
Implementing Interfaces
A class implements an interface using the implements keyword:
public class Circle extends Shape implements Drawable {
// ... Shape implementation ...
@Override
public void draw() {
System.out.println("Drawing a " + color + " circle with radius " + radius);
}
}
Multiple Interface Implementation
Unlike classes (single inheritance), a class can implement multiple interfaces:
public interface Printable {
void print();
}
public interface Saveable {
void save(String filename);
}
// A class can implement multiple interfaces
public class Document implements Printable, Saveable, Drawable {
@Override
public void print() {
System.out.println("Printing document...");
}
@Override
public void save(String filename) {
System.out.println("Saving to " + filename);
}
@Override
public void draw() {
System.out.println("Drawing document preview...");
}
}
This enables "mix-in" style programming where classes gain capabilities by implementing interfaces.
Abstract Classes vs Interfaces: Making the Choice
| Feature | Abstract Class | Interface |
|---|---|---|
| Multiple inheritance | No (single parent) | Yes (many interfaces) |
| Instance variables | Yes | No (only constants) |
| Constructors | Yes | No |
| Access modifiers | Any | Public only (mostly) |
| Method implementations | Any (abstract or concrete) | Default/static (or abstract) |
| Use case | IS-A relationship with shared code | CAN-DO capability |
The IS-A vs CAN-DO Rule
Abstract class: Use when subclass "IS-A" type of the parent
- Dog IS-A Animal
- Circle IS-A Shape
Interface: Use when class "CAN-DO" something
- Dog CAN-DO Runnable
- Circle CAN-DO Drawable
A Practical Example
// Abstract class for shared code and IS-A relationship
public abstract class Animal {
protected String name;
public Animal(String name) {
this.name = name;
}
public abstract void makeSound();
public void sleep() {
System.out.println(name + " is sleeping");
}
}
// Interfaces for CAN-DO capabilities
public interface Swimmable {
void swim();
}
public interface Flyable {
void fly();
}
// A duck IS-A animal that CAN swim and CAN fly
public class Duck extends Animal implements Swimmable, Flyable {
public Duck(String name) {
super(name);
}
@Override
public void makeSound() {
System.out.println(name + " says: Quack!");
}
@Override
public void swim() {
System.out.println(name + " is swimming");
}
@Override
public void fly() {
System.out.println(name + " is flying");
}
}
// A fish IS-A animal that CAN swim (but not fly)
public class Fish extends Animal implements Swimmable {
public Fish(String name) {
super(name);
}
@Override
public void makeSound() {
System.out.println(name + " says: Blub!");
}
@Override
public void swim() {
System.out.println(name + " is swimming");
}
}
Interface Evolution: Default Methods
Default methods solve a major problem: how do you add new methods to an interface without breaking all existing implementations?
public interface Collection<E> {
// Original methods
boolean add(E element);
int size();
// Added in Java 8 - doesn't break existing implementations
default boolean isEmpty() {
return size() == 0;
}
// Implementations can override if they have a more efficient way
}
Resolving Default Method Conflicts
When a class implements multiple interfaces with the same default method:
interface A {
default void hello() { System.out.println("Hello from A"); }
}
interface B {
default void hello() { System.out.println("Hello from B"); }
}
class C implements A, B {
// Must override to resolve conflict
@Override
public void hello() {
A.super.hello(); // Choose one, or provide new implementation
}
}
Abstract classes and interfaces both enable polymorphism but differently. Abstract classes share code among related classes—they can have state and constructors. Interfaces define capabilities that unrelated classes can implement—multiple inheritance of type. Use abstract classes for "IS-A" relationships with shared implementation. Use interfaces for "CAN-DO" capabilities. A class can extend one abstract class but implement many interfaces.
