Abstract Classes & Interfaces

Java · Lesson 18 of 32 6 min read Updated

Sometimes a parent class should never be used on its own. There is no such thing as a plain "worker" who is neither full-time nor a contractor; there is no plain "shape" that is not a circle or a rectangle. Java lets you say this with abstract classes: classes that exist only to be extended.

Interfaces go one step further. An interface is a pure contract, a list of things a class promises it can do, with no connection to any family tree. A smart bulb and a smart speaker are unrelated classes, but both can promise to be Switchable. In this lesson you will write both and learn when to choose which.

Abstract classes and methods

Mark a class abstract to stop anyone creating objects of it directly. An abstract class can declare abstract methods: methods with no body, ending in a semicolon. Every concrete (non-abstract) subclass must provide a body for each of them. An abstract class can still have fields, constructors and normal methods that use the abstract ones.

Java
public class Payroll {
    public static void main(String[] args) {
        Worker[] team = {
            new FullTimer("Kabir", 60000),
            new Contractor("Ananya", 800, 120)
        };
        for (Worker w : team) {
            w.printPayslip();
        }
    }
}

abstract class Worker {
    private final String name;

    Worker(String name) {
        this.name = name;
    }

    abstract double grossPay();          // no body: subclasses must provide it

    double tax() {
        return grossPay() * 0.10;        // a concrete method using the abstract one
    }

    void printPayslip() {
        System.out.printf("%-7s gross Rs %,9.2f  tax Rs %,8.2f  net Rs %,9.2f%n",
                name, grossPay(), tax(), grossPay() - tax());
    }
}

class FullTimer extends Worker {
    private final double monthlySalary;

    FullTimer(String name, double monthlySalary) {
        super(name);
        this.monthlySalary = monthlySalary;
    }

    @Override
    double grossPay() {
        return monthlySalary;
    }
}

class Contractor extends Worker {
    private final double hourlyRate;
    private final int hours;

    Contractor(String name, double hourlyRate, int hours) {
        super(name);
        this.hourlyRate = hourlyRate;
        this.hours = hours;
    }

    @Override
    double grossPay() {
        return hourlyRate * hours;
    }

    @Override
    double tax() {
        return grossPay() * 0.02;        // a lower flat rate in this example
    }
}
Output
Kabir   gross Rs 60,000.00  tax Rs 6,000.00  net Rs 54,000.00
Ananya  gross Rs 96,000.00  tax Rs 1,920.00  net Rs 94,080.00

printPayslip is written once in Worker, yet it calls grossPay(), which each subclass defines differently. The parent fixes the overall steps and the children fill in the details; this design is called the template method pattern.

The compiler enforces the rules. new Worker("x") fails with error: Worker is abstract; cannot be instantiated. A subclass that forgets grossPay fails with error: Intern is not abstract and does not override abstract method grossPay() in Worker.

Interfaces

An interface lists methods that implementing classes must provide. A class signs the contract with implements, and it can implement several interfaces at once, even though it can extend only one class. An interface can also contain:

  • default methods (Java 8): methods with a body, which implementing classes get for free and may override.
  • static methods (Java 8): helper methods called on the interface name.
  • private methods (Java 9): helpers shared by the default methods.
  • constants: every field in an interface is automatically public static final.

Interface methods without a body are automatically public and abstract.

Java
public class Devices {
    public static void main(String[] args) {
        SmartBulb bulb = new SmartBulb("Bedroom bulb");
        SmartSpeaker speaker = new SmartSpeaker("Hall speaker");

        Switchable[] things = {bulb, speaker};
        for (Switchable s : things) {
            s.turnOn();
            System.out.println(s.status());
        }

        Chargeable c = speaker;
        System.out.println("Battery: " + c.batteryPercent() + "%");
        System.out.println(c.needsCharging());
        System.out.println(Switchable.describeAll(things));
    }
}

interface Switchable {
    void turnOn();                               // abstract by default
    boolean isOn();

    default String status() {                    // default method: has a body
        return (isOn() ? "ON  " : "OFF ") + label();
    }

    String label();

    static String describeAll(Switchable[] items) {
        int on = 0;
        for (Switchable s : items) {
            if (s.isOn()) on++;
        }
        return on + " of " + items.length + " devices are on";
    }
}

interface Chargeable {
    int LOW_BATTERY = 20;                        // a constant: public static final

    int batteryPercent();

    default boolean needsCharging() {
        return batteryPercent() < LOW_BATTERY;
    }
}

class SmartBulb implements Switchable {
    private final String name;
    private boolean on;

    SmartBulb(String name) { this.name = name; }

    public void turnOn() { on = true; }
    public boolean isOn() { return on; }
    public String label() { return name; }
}

class SmartSpeaker implements Switchable, Chargeable {
    private final String name;
    private boolean on;

    SmartSpeaker(String name) { this.name = name; }

    public void turnOn() { on = true; }
    public boolean isOn() { return on; }
    public String label() { return name; }
    public int batteryPercent() { return 15; }
}
Output
ON  Bedroom bulb
ON  Hall speaker
Battery: 15%
true
2 of 2 devices are on

An interface is also a type. A Switchable[] array holds a bulb and a speaker side by side, and a Chargeable variable can refer to the speaker. Code that works with Switchable does not care what the object really is, only that it keeps the promise. The implementing methods must be public; leaving public off turnOn gives error: ... attempting to assign weaker access privileges; was public.

A real interface: Comparable

The standard library is full of interfaces. One you will use often is Comparable, which tells Java how to put objects in order. Implement its single method compareTo: return a negative number if this comes first, a positive number if other comes first, and 0 if they are equal. Then Arrays.sort can sort your objects.

Java
import java.util.Arrays;

public class Ranking {
    public static void main(String[] args) {
        Student[] students = {
            new Student("Meera", 88),
            new Student("Aarav", 95),
            new Student("Rohan", 72)
        };
        Arrays.sort(students);                    // uses compareTo
        System.out.println(Arrays.toString(students));
    }
}

class Student implements Comparable<Student> {
    final String name;
    final int marks;

    Student(String name, int marks) {
        this.name = name;
        this.marks = marks;
    }

    @Override
    public int compareTo(Student other) {
        return Integer.compare(other.marks, this.marks);   // highest marks first
    }

    @Override
    public String toString() {
        return name + "(" + marks + ")";
    }
}
Output
[Aarav(95), Meera(88), Rohan(72)]

Comparable<Student> uses generics: the <Student> says what it compares against. You will learn generics in Collections & Generics. Integer.compare(a, b) safely produces the negative, zero or positive result; swapping the arguments, as here, reverses the order.

Interfaces with exactly one abstract method, like Comparable, are called functional interfaces. They are the foundation of lambdas, covered in Lambdas & Functional Interfaces.

Abstract class or interface?

Abstract classInterface
A class can haveone parentmany interfaces
Fieldsany kind, including instance fieldsonly constants
Constructorsyesno
Relationship"is a kind of" (a contractor is a worker)"can do" (a speaker can be switched, charged)

A good rule: start with an interface to describe what something can do. Use an abstract class when related classes share real state (fields) and code. The two combine well: an abstract class can implement an interface and leave some methods for its subclasses.

Common mistakes

  • Trying to create an abstract class or interface with new. Create an object of a concrete subclass instead.
  • Forgetting to implement an abstract method. Either implement every abstract method or mark the subclass abstract too.
  • Leaving out public when implementing interface methods. They must stay public.
  • Putting instance state in an interface. Interface fields are constants. Keep changing state in classes.
  • Inconsistent compareTo. If a.compareTo(b) is negative, b.compareTo(a) must be positive, or sorting gives strange results.

Key points

  • An abstract class cannot be instantiated and may declare abstract methods without bodies.
  • Concrete subclasses must implement every inherited abstract method.
  • An interface is a contract; classes implements it and can implement many interfaces.
  • Interfaces may contain default, static and private methods, and constants.
  • Comparable<T> with compareTo lets Arrays.sort order your objects.
  • Prefer interfaces for "can do" abilities; use abstract classes for shared state and code.

Exercise

Create an interface Shape with double area(), double perimeter() and a default method describe() that returns the class name with the area and perimeter to two decimal places. Implement it in Circle (radius) and Rectangle (length, width). Put a circle of radius 3.5 and a rectangle of 12 by 8 in a Shape[] array and print each description and the total area.

Show solution
Java
public class Shapes {
    public static void main(String[] args) {
        Shape[] shapes = {new Circle(3.5), new Rectangle(12, 8)};
        double totalArea = 0;
        for (Shape s : shapes) {
            System.out.println(s.describe());
            totalArea += s.area();
        }
        System.out.printf("Total area: %.2f%n", totalArea);
    }
}

interface Shape {
    double area();
    double perimeter();

    default String describe() {
        return "%s: area %.2f, perimeter %.2f".formatted(
                getClass().getSimpleName(), area(), perimeter());
    }
}

class Circle implements Shape {
    private final double radius;

    Circle(double radius) { this.radius = radius; }

    public double area() { return Math.PI * radius * radius; }
    public double perimeter() { return 2 * Math.PI * radius; }
}

class Rectangle implements Shape {
    private final double length;
    private final double width;

    Rectangle(double length, double width) {
        this.length = length;
        this.width = width;
    }

    public double area() { return length * width; }
    public double perimeter() { return 2 * (length + width); }
}
Output
Circle: area 38.48, perimeter 21.99
Rectangle: area 96.00, perimeter 40.00
Total area: 134.48

describe() is written once in the interface. It calls area() and perimeter(), which each class implements in its own way.

Quick Quiz

1. Which statement about abstract classes is true?

2. How many interfaces can a Java class implement?

3. What is a default method in an interface?

4. compareTo returns a negative number. What does that mean?

Learn Java with a mentor

Free lessons get you started. Our Java, C & C++ Programming training adds real projects and code reviews from working developers.

  • Project-based learning
  • Mentor code reviews
  • Free counselling call