Java Arena
Step 2 of 9

Many classes, many interfaces

One interface, many classes

Step 1's heater isn't the only switchable device in the app. Every kind of device can implement Switchable, and each one keeps the promise in its own way. A garden light counts how many times it has been switched on, since its bulb wears out:

Main.java
public class Main {
    public static void main(String[] args) {
        Heater heater = new Heater("hall", 21);
        GardenLight light = new GardenLight("patio");
        heater.turnOn();
        light.turnOn();
        light.turnOn();
        light.turnOff();
        light.turnOn();
        System.out.println(heater);
        System.out.println(light);
        System.out.println(heater.isOn() + " " + light.isOn());
    }
}
Switchable.java
public interface Switchable {
    void turnOn();
    void turnOff();
    boolean isOn();
}
Heater.java
public class Heater implements Switchable {
    private String room;
    private int degrees;
    private boolean on;

    public Heater(String room, int degrees) {
        this.room = room;
        this.degrees = degrees;
        this.on = false;
    }

    @Override
    public void turnOn() {
        this.on = true;
    }

    @Override
    public void turnOff() {
        this.on = false;
    }

    @Override
    public boolean isOn() {
        return this.on;
    }

    @Override
    public String toString() {
        if (this.on) {
            return "Heater in the " + this.room + ": on, " + this.degrees + " degrees";
        }
        return "Heater in the " + this.room + ": off";
    }
}
GardenLight.java
public class GardenLight implements Switchable {
    private String place;
    private boolean on;
    private int switchOns;

    public GardenLight(String place) {
        this.place = place;
        this.on = false;
        this.switchOns = 0;
    }

    @Override
    public void turnOn() {
        if (!this.on) {
            this.on = true;
            this.switchOns++;
        }
    }

    @Override
    public void turnOff() {
        this.on = false;
    }

    @Override
    public boolean isOn() {
        return this.on;
    }

    @Override
    public String toString() {
        return "Garden light (" + this.place + "): " + this.switchOns + " switch-ons";
    }
}
Output
Heater in the hall: on, 21 degrees
Garden light (patio): 2 switch-ons
true true
  • Both classes have the three methods that Switchable lists, with the same headers. What the methods do is up to each class: the light's turnOn counts, but only when the light was off.
  • Heater and GardenLight share no code and no superclass (other than Object). The interface is all they have in common, and that's all the app's buttons need, as the next steps show.

One class, many interfaces

Some devices run on a battery. What the app needs from them is another interface:

public interface Chargeable {
    int batteryPercent();
    void charge(int minutes);
}

A robot vacuum can be switched on, and it has a battery. A class can implement several interfaces: list them after implements, separated by commas.

Main.java
public class Main {
    public static void main(String[] args) {
        RobotVacuum vacuum = new RobotVacuum();
        vacuum.clean(10);
        System.out.println(vacuum.batteryPercent());
        vacuum.turnOn();
        vacuum.clean(10);
        System.out.println(vacuum.batteryPercent());
        vacuum.turnOff();
        vacuum.charge(45);
        System.out.println(vacuum.batteryPercent() + "%, on: " + vacuum.isOn());
    }
}
Switchable.java
public interface Switchable {
    void turnOn();
    void turnOff();
    boolean isOn();
}
Chargeable.java
public interface Chargeable {
    int batteryPercent();
    void charge(int minutes);
}
RobotVacuum.java
public class RobotVacuum implements Switchable, Chargeable {
    private boolean on;
    private int battery;

    public RobotVacuum() {
        this.on = false;
        this.battery = 50;
    }

    @Override
    public void turnOn() {
        this.on = true;
    }

    @Override
    public void turnOff() {
        this.on = false;
    }

    @Override
    public boolean isOn() {
        return this.on;
    }

    @Override
    public int batteryPercent() {
        return this.battery;
    }

    @Override
    public void charge(int minutes) {
        this.battery += minutes;
        if (this.battery > 100) {
            this.battery = 100;
        }
    }

    public void clean(int minutes) {
        if (this.on) {
            this.battery -= 2 * minutes;
            if (this.battery < 0) {
                this.battery = 0;
            }
        }
    }
}
Output
50
30
75%, on: false
  • implements Switchable, Chargeable makes two promises, and RobotVacuum keeps both: it has all five methods. Leave one out, and javac says which one is missing, and from which interface.
  • clean is RobotVacuum's own. Neither interface knows about it.
  • The vacuum's battery isn't in either interface. The interfaces only say that there's a batteryPercent() to ask, and the class decides how to keep the number.

extends and implements together

A class can extend only one class, but it can implement any number of interfaces, and it can do both at once. extends comes first. If the app had an abstract class Appliance for the name and the power of every device (module 34), the vacuum's header could be:

public class RobotVacuum extends Appliance implements Switchable, Chargeable {

Then a RobotVacuum would get Appliance's code, and still promise the methods of both interfaces. Step 9 comes back to when to use which.

Your task

Challenge 1 of 3

The family's electric scooter shows up in the app too. Switchable.java and Chargeable.java are ready. In ElectricScooter.java, write the class ElectricScooter, which implements both interfaces, with:

  • a constructor ElectricScooter(int battery), which takes the battery's percentage; a new scooter is off
  • turnOn(), turnOff() and isOn(), as for any Switchable
  • batteryPercent(), which returns the battery's percentage
  • charge(int minutes), which adds 2 percent for every minute, but never goes above 100
  • ride(int km), which uses 5 percent for every kilometer, but only when the scooter is on, and never goes below 0

Put @Override above each method that an interface asks for. The check runs:

ElectricScooter scooter = new ElectricScooter(40);
scooter.ride(3);
System.out.println(scooter.batteryPercent());
scooter.turnOn();
scooter.ride(3);
System.out.println(scooter.isOn() + " " + scooter.batteryPercent());
scooter.charge(50);
System.out.println(scooter.batteryPercent());

and expects:

40
true 25
100

Challenge 2

A laptop and a pair of headphones both implement Chargeable, but they charge at different speeds. What does this program print?

Challenge 3

The app can dim some lights. CeilingLight and DeskLamp both say implements Dimmable, and both have @Override above their methods setBrightness and getBrightness. But Dimmable.java is still empty, so nothing compiles. Write the interface Dimmable, with the two methods: setBrightness, which takes a percentage as an int and returns nothing, and getBrightness, which returns the percentage as an int. Don't change the classes. The program should print:

70 50
100 0