Java Arena
Step 8 of 8

Abstract classes and abstract methods

Step 2's program made new Instrument(), a "shaker". But a music school has no instruments that are just instruments: each one is a flute, a drum, a violin. And some methods can't be written for "an instrument" at all. What sound does an instrument make?

Java has a way to say this: an abstract class. It's a class made only to be extended. You can't create an object of it with new, and it can leave methods for its subclasses to write.

Main.java
import java.util.ArrayList;

public class Main {
    public static void main(String[] args) {
        ArrayList<Instrument> band = new ArrayList<>();
        band.add(new Flute("Silver flute"));
        band.add(new Drum("Snare drum"));
        for (Instrument instrument : band) {
            instrument.play();
        }
    }
}
Instrument.java
public abstract class Instrument {
    private String name;

    public Instrument(String name) {
        this.name = name;
    }

    public String getName() {
        return this.name;
    }

    public abstract String sound();

    public void play() {
        System.out.println(this.name + ": " + this.sound());
    }
}
Flute.java
public class Flute extends Instrument {
    public Flute(String name) {
        super(name);
    }

    @Override
    public String sound() {
        return "fweee";
    }
}
Drum.java
public class Drum extends Instrument {
    public Drum(String name) {
        super(name);
    }

    @Override
    public String sound() {
        return "boom boom";
    }
}
Output
Silver flute: fweee
Snare drum: boom boom
  • public abstract class Instrument makes Instrument abstract.
  • public abstract String sound(); is an abstract method: a header followed by a semicolon, with no body. It says that every instrument has a sound() that returns a String, and leaves the code to the subclasses.
  • Everything else is as in any class: an instance variable, a constructor, which the subclasses call with super(name), and ordinary methods such as getName and play.
  • play is written once, in Instrument, and calls sound(). As in step 6, the version that runs is the object's own: the flute's, then the drum's.
  • Variables and lists of the abstract type are fine: ArrayList<Instrument> holds flutes and drums. It just can never hold an object that is only an Instrument.

No new

Main.java
public class Main {
    public static void main(String[] args) {
        Instrument mystery = new Instrument("Mystery");
        mystery.play();
    }
}
Instrument.java
public abstract class Instrument {
    private String name;

    public Instrument(String name) {
        this.name = name;
    }

    public abstract String sound();

    public void play() {
        System.out.println(this.name + ": " + this.sound());
    }
}
What javac prints
Main.java:3: error: Instrument is abstract; cannot be instantiated
        Instrument mystery = new Instrument("Mystery");
                             ^
1 error

Think about what mystery.play() would have to do: call sound(), which has no code to run. So Java doesn't let an abstract class's object exist on its own. Instrument's constructor still runs, but only as the first part of building a Flute or a Drum.

A subclass must write the missing methods

A class that extends Instrument must write every abstract method, or it doesn't compile. Here a harp forgot its sound:

Main.java
public class Main {
    public static void main(String[] args) {
        Harp harp = new Harp("Concert harp");
        harp.play();
    }
}
Instrument.java
public abstract class Instrument {
    private String name;

    public Instrument(String name) {
        this.name = name;
    }

    public abstract String sound();

    public void play() {
        System.out.println(this.name + ": " + this.sound());
    }
}
Harp.java
public class Harp extends Instrument {
    public Harp(String name) {
        super(name);
    }
}
What javac prints
Harp.java:1: error: Harp is not abstract and does not override abstract method sound() in Instrument
public class Harp extends Instrument {
       ^
1 error
  • The fix is to write Harp's sound(). javac's other way out, making Harp abstract too, means no harps could be made either. That fits only for a group of instruments, such as a StringInstrument between Instrument and Harp that leaves sound() to each of its own subclasses.
  • Only an abstract class may have abstract methods. A plain class with an abstract method doesn't compile.
  • A class can be abstract without any abstract methods. Then abstract just forbids new.

Why not an ordinary class?

Instrument could be a plain class, with sound() returning "" as a placeholder. Then new Instrument(...) would make an instrument without a sound, and a subclass that forgot to override sound() would quietly play nothing. The abstract method turns both mistakes into errors that javac catches, and it tells anyone reading the class what a subclass must provide.

Module 35's interfaces take this idea further: they list methods that a class promises to have, without being its superclass.

Your task

Challenge 1 of 3

The courier makes Shipment abstract: every shipment has a price, but there's no price for "a shipment" in general. Shipment.java and Letter.java are ready. In Pallet.java, write the class Pallet, which extends Shipment:

  • a constructor Pallet(String code, int boxes)
  • price(), with @Override above it, which returns 20 dollars plus 5 for every box

The check runs:

Shipment pallet = new Pallet("T-40", 6);
System.out.println(pallet.label());
System.out.println(new Letter("L-3").label());
System.out.println(new Pallet("T-41", 0).price());

and expects:

T-40: 50 dollars
L-3: 2 dollars
20

Challenge 2

In a museum, every exhibit is kept in some room, but which room depends on the kind of exhibit. Fill in the two blanks, the same word in both, so that Exhibit can't be created with new, and its subclasses must write room().

Challenge 3

Now write an abstract class yourself. The library's films and audiobooks are ready in Film.java and Audiobook.java: each extends LibraryItem, hands its title to super(title), and overrides loanDays(). In LibraryItem.java, write the abstract class LibraryItem, with:

  • a private title, and a constructor LibraryItem(String title)
  • getTitle()
  • an abstract method int loanDays()
  • dueDay(int today), which returns the number of the day the item is due back: today's number plus the item's loan days

The check runs:

LibraryItem film = new Film("Night Train");
LibraryItem audio = new Audiobook("Salt and Stars");
System.out.println(film.getTitle() + ": day " + film.dueDay(10));
System.out.println(audio.getTitle() + ": day " + audio.dueDay(10));

and expects:

Night Train: day 17
Salt and Stars: day 24