Course outline · 0% complete

0/29 lessons0%

Course overview →

Abstract classes and interfaces

lesson 6-3 · ~12 min · 18/29

Abstract classes

Inheritance as you have it forces the superclass to implement everything, even methods that have no sensible parent version. A wrong default is a trap, because forgetting to override it leaves a program that runs while silently producing nonsense, which is precisely what Shape.area() returning 0.0 invited in lesson 6-2.

This lesson's two tools let a class demand behavior from its children instead of faking it, which is also how libraries safely hand you half-finished machinery to complete.

An abstract class can declare methods with no body:

abstract class Animal {
  abstract void speak();      // no body, subclasses must provide it

  void speakTwice() {         // normal method, inherited as usual
    speak();
    speak();
  }
}

new Animal() no longer compiles, since there is no complete animal to build. Any concrete subclass must override speak() or the compiler refuses it, which turns a forgotten override from a silent bug into a build failure.

Note that speakTwice calls speak() before any subclass exists. Polymorphism guarantees the right version runs later, so a parent can write a whole workflow around a step it does not know how to perform.

A class fulfilling two obligations

Duck inherits from an abstract class and implements an interface at the same time.

public class Main {
  public static void main(String[] args) {
    Duck d = new Duck();
    d.speakTwice();
    d.swim();
  }
}

abstract class Animal {
  abstract void speak();

  void speakTwice() {
    speak();
    speak();
  }
}

interface Swimmer {
  void swim();
}

class Duck extends Animal implements Swimmer {
  @Override
  void speak() {
    System.out.println("quack");
  }

  @Override
  public void swim() {
    System.out.println("paddling");
  }
}

Output

quack
quack
paddling

speak is required by the abstract class and swim by the interface, so Duck has to supply both. speakTwice arrives free from Animal and calls the quack twice.

Note that swim is declared public while speak is not. Interface methods are implicitly public, and an implementation may never reduce the visibility it promised.

Interfaces

An interface is a pure contract, a list of methods a class promises to provide:

interface Swimmer {
  void swim();   // implicitly public and abstract
}

class Duck extends Animal implements Swimmer { ... }

The two tools overlap enough to need a comparison:

Abstract classInterface
Fields with stateyesno, only constants
Method bodiesyesonly default methods
How many per classone, via extendsmany, via implements A, B
Relationship expressedis-acan-do

The rule of thumb follows from that last row. Shared state and code belong in an abstract class, while a capability that many unrelated classes can have, such as Comparable, Swimmer, or Serializable, belongs in an interface.

Interface methods are public, so implementations must say public as well. Modern Java also allows default methods with bodies in an interface, which exist mainly so a library can add a method without breaking every class that already implements it.

abstract Animal fields + shared code Swimmer Flyer class Duck extends (one) implements (many) is-a comes from the class, can-do comes from the interfaces
A class extends exactly one superclass for shared state and code, and implements any number of interfaces for capabilities.

Combining a superclass with capabilities

A class that needs shared fields from Animal and must also promise the capabilities Swimmer and Flyer is declared:

class Duck extends Animal implements Swimmer, Flyer {
  ...
}

Java allows exactly one superclass through extends and any number of interfaces through implements, separated by commas. The extends clause comes first.

The division of labor is the point. Fields and shared code come from the class, while capabilities come from the interfaces, so a Duck and a Submarine can both be Swimmer without sharing an ancestor.

That is also why the single-superclass limit is rarely painful in practice. Anything a class needs to be able to do can arrive as an interface, and only its identity comes from the one parent.

Polymorphism through an interface

The loop knows only the contract, never the concrete classes.

public class Main {
  public static void main(String[] args) {
    Instrument[] band = { new Piano(), new Drum(), new Piano() };
    for (Instrument i : band) {
      i.play();
    }
  }
}

interface Instrument {
  void play();
}

class Piano implements Instrument {
  @Override
  public void play() {
    System.out.println("plink");
  }
}

class Drum implements Instrument {
  @Override
  public void play() {
    System.out.println("boom");
  }
}

Output

plink
boom
plink

The interface body is a single line, void play();, with no implementation anywhere in it. Each class declares implements Instrument and supplies public void play().

Piano and Drum share no parent class and no fields, only the promise. That is the case an interface handles and inheritance cannot, and it is why library types such as Comparable are interfaces rather than base classes.