Interfaces in Java — exercises with shapes, a library and sensors (and the cast that worked until a T-shirt showed up)

I had a catalog of books, and I happily wrote Book b = (Book) v; inside a loop over an array of Sellable. It worked on Monday, Tuesday and Wednesday. On Thursday I added a T-shirt to the catalog and the program crashed with a ClassCastException. The cast hadn’t changed. What had changed was the content of the array. The compiler let me write the cast because it could be right. The program failed because, this time, it wasn’t.

I learned the hard way that, with interfaces, the type of the variable doesn’t tell you which object is inside. Today we’ll practice exactly that: three programs of growing difficulty, three snippets to debug and a cheat sheet to take home.

These exercises build on the theory of interfaces and review what we saw in the practice of interfaces. You can do them without those two, but it will be harder.

How to use this article

  1. Read the task of a level and don’t look at the solution yet.
  2. Write your own version, compile it and compare your output with the expected one.
  3. If you get stuck, look only at the 🔵 hint and try again.
  4. When you finish, compare with the solution and look at why it’s written that way, not just whether it prints the same thing.

No exercise uses Scanner. The data is hard-coded so your output is identical to the expected one and you can check it at a glance.

Level 1 — Basic: shapes with Measurable

Task. Create an interface Measurable with:

  • double area()
  • String name()
  • a default String describe() method that returns the name and the area with two decimals, for example Circle with area 12.57.

Create three classes that implement it: Rectangle (width and height), Circle (radius) and Square (side). Their name() methods return "Rectangle", "Circle" and "Square". In a Shapes class with a main, store in an array of type Measurable a 3 × 4 rectangle, a circle of radius 2 and a square of side 3.5. Write two static methods:

  • totalArea(Measurable[]): adds up the areas.
  • largest(Measurable[]): returns the shape with the biggest area.

The main prints the description of each shape, the total area (as Total area: 36.82) and the largest shape (as Largest: followed by its description).

Expected output:

Rectangle with area 12.00
Circle with area 12.57
Square with area 12.25
Total area: 36.82
Largest: Circle with area 12.57

describe() doesn’t need to know which shape it is: name() and area() are enough, and both are already part of the contract. For largest, start from the first shape and compare it with the rest.

Level 2 — Intermediate: a library

Task. A library lends books, rents out rooms and lends laptops that can also be booked by day. Model it with interfaces:

  • Resource: has a String name().
  • Lendable extends Resource: boolean lend(String member), void giveBack(), boolean available() and a default String status() that returns name: available or name: on loan.
  • Bookable extends Resource: boolean book(String member, String day) (days from Monday to Friday; it returns false if the day is already taken or doesn’t exist), String bookedBy(String day) (null if the day is free) and a default boolean isFree(String day).
  • Book is Lendable, Room is Bookable and Laptop is both.

In Library.main, create a Resource[] array with the book “Java for FP2”, “Room 3” and “Laptop 07”, and run this script:

  1. Ana borrows everything that can be lent.
  2. Luis tries to borrow the book.
  3. Luis books Room 3 on Monday.
  4. Marta books Room 3 on Monday.
  5. Marta books Laptop 07 on Tuesday.
  6. Print the status of every resource (the loan status of whatever is lendable, and the Monday and Tuesday bookings of whatever is bookable).

Use these messages: Ana borrows Java for FP2: OK (or unavailable), Luis books Room 3 on Monday: OK (or taken), then a --- Status --- line, and for bookable resources Room 3: Monday: booked by Luis, Tuesday: free.

Expected output:

Ana borrows Java for FP2: OK
Ana borrows Laptop 07: OK
Luis borrows Java for FP2: unavailable
Luis books Room 3 on Monday: OK
Marta books Room 3 on Monday: taken
Marta books Laptop 07 on Tuesday: OK
--- Status ---
Java for FP2: on loan
Room 3: Monday: booked by Luis, Tuesday: free
Laptop 07: on loan
Laptop 07: Monday: free, Tuesday: booked by Marta

The array is of type Resource, so before lending or booking you have to ask first with instanceof Lendable / instanceof Bookable. The laptop answers yes to both questions, which is why it shows up twice in the status.

Level 3 — Challenge: a sensor station

Task. A weather station has sensors of several kinds. Define:

  • Sensor: String id(), String unit(), double read(), a static method format(double value, String unit) that returns the value with one decimal and the unit (27.5 °C), and a default String summary() that returns id (unit).
  • Calibratable extends Sensor: void calibrate(double offset) and a default summary() that adds , calibratable to the one from Sensor.
  • Alarmable extends Sensor: double threshold(), a default boolean inAlarm() (read() > threshold()) and a default summary() that adds , alarm > threshold to the one from Sensor (for example , alarm > 30.0 °C).
  • Thermometer implements **Calibratable and Alarmable**, so it gets two conflicting summary() methods. Resolve it so that it shows T1 (°C), alarm > 30.0 °C, calibratable.
  • Humidity is only a Sensor (unit %). Pressure is only Alarmable (unit hPa).

In Station, create the array with: thermometers T1 (27.5 °C, threshold 30) and T2 (32.1 °C, threshold 30), a humidity sensor H1 (61.0 %) and a pressure sensor P1 (1013.2 hPa, threshold 1020). Write a method report(Sensor[], String title) that prints a === title === line, then each summary followed by -> and its reading, adds ⚠ ALARM when it applies, and ends with Alarms: n. Print the report with the title “Initial report”, calibrate every calibratable sensor by +3.0 and print it again with the title “After calibrating”.

Expected output:

=== Initial report ===
T1 (°C), alarm > 30.0 °C, calibratable -> 27.5 °C
T2 (°C), alarm > 30.0 °C, calibratable -> 32.1 °C  ⚠ ALARM
H1 (%) -> 61.0 %
P1 (hPa), alarm > 1020.0 hPa -> 1013.2 hPa
Alarms: 1
=== After calibrating ===
T1 (°C), alarm > 30.0 °C, calibratable -> 30.5 °C  ⚠ ALARM
T2 (°C), alarm > 30.0 °C, calibratable -> 35.1 °C  ⚠ ALARM
H1 (%) -> 61.0 %
P1 (hPa), alarm > 1020.0 hPa -> 1013.2 hPa
Alarms: 2

In Thermometer, summary() has to call one of the two versions with Interface.super.summary() and add whatever is missing. Notice that the summary printed for a thermometer has both suffixes (alarm and calibratable) but the prefix T1 (°C) only once.

What’s wrong here?

Three snippets, one bug each. Before reading the explanation, do two things: predict whether it compiles and, if it does, what it prints (or what happens when you run it).

Snippet 1 — The cast that worked until a T-shirt showed up

interface Sellable {
    String name();
}

class Book implements Sellable {
    public String name() { return "Book"; }
    public String author() { return "Sergio"; }
}

class TShirt implements Sellable {
    public String name() { return "T-shirt"; }
}

public class F1 {
    public static void main(String[] args) {
        Sellable[] catalog = { new Book(), new TShirt() };

        for (Sellable v : catalog) {
            Book l = (Book) v;
            System.out.println(l.name() + " by " + l.author());
        }
    }
}

Think about it before moving on: does it compile? What does it print?

Explanation. It compiles just fine: the compiler accepts a cast from Sellable to Book because some Sellable could be a Book. But on the second turn of the loop, v is a TShirt:

Book by Sergio
Exception in thread "main" java.lang.ClassCastException: class TShirt cannot be cast to class Book (TShirt and Book are in unnamed module of loader 'app')
	at F1.main(F1.java:19)

“ClassCastException … cannot be cast to class …”. A downcast is a promise to the compiler that gets checked at run time. If the object isn’t of that class, the program crashes. The first line (“Book by Sergio”) did get printed, because the first object really was a Book.

Fix. Ask before you convert:

for (Sellable v : catalog) {
    if (v instanceof Book l) {
        System.out.println(l.name() + " by " + l.author());
    } else {
        System.out.println(v.name() + " (no author)");
    }
}
Book by Sergio
T-shirt (no author)

With instanceof Book l, the check and the conversion happen in a single step, and l only exists inside the if. There’s no way to forget the check.

Snippet 2 — Two greetings, no winner

interface Friendly {
    default String greeting() { return "Hi"; }
}

interface Formal {
    default String greeting() { return "Good morning"; }
}

class Receptionist implements Friendly, Formal {
}

public class F2 {
    public static void main(String[] args) {
        System.out.println(new Receptionist().greeting());
    }
}

Does it compile? What does it print?

Explanation. It doesn’t compile. Receptionist inherits two default greeting() methods and Java doesn’t know which one to use:

F2.java:9: error: types Friendly and Formal are incompatible;
class Receptionist implements Friendly, Formal {
^
  class Receptionist inherits unrelated defaults for greeting() from types Friendly and Formal
1 error

“inherits unrelated defaults for greeting() from types …”. Two interfaces, the same default, and no decision: the diamond. Java would rather force you to decide than pick wrong for you.

Fix. The class overrides the method and decides what to do, calling each version with Interface.super:

class Receptionist implements Friendly, Formal {
    @Override
    public String greeting() {                         // we decide
        return Formal.super.greeting() + ", " + Friendly.super.greeting().toLowerCase();
    }
}
Good morning, hi

Snippet 3 — The constant that wouldn’t budge

interface Config {
    int RETRIES = 3;
}

class Client implements Config {
    void connect() {
        while (RETRIES > 0) {
            System.out.println("Trying… attempts left: " + RETRIES);
            RETRIES--;
        }
    }
}

public class F3 {
    public static void main(String[] args) {
        new Client().connect();
    }
}

Does it compile? What does it print?

Explanation. It doesn’t compile. RETRIES is declared in an interface, so it’s public static final even if you don’t write it. And a constant can’t be modified:

F3.java:9: error: cannot assign a value to static final variable RETRIES
            RETRIES--;
            ^
1 error

“cannot assign a value to static final variable”. The fields of an interface are constants, no matter how many classes inherit them. If you need a counter that changes, it isn’t a constant.

Fix. Copy the value into a local variable and modify the copy:

class Client implements Config {
    void connect() {
        int remaining = RETRIES;                  // local copy: this one can change
        while (remaining > 0) {
            System.out.println("Trying… attempts left: " + remaining);
            remaining--;
        }
    }
}
Trying… attempts left: 3
Trying… attempts left: 2
Trying… attempts left: 1

Solutions

Look at this section only after you’ve tried the exercise. If your code is different but gives the same output and honors the contracts, it’s a valid solution too.

Level 1 — Solution

Measurable.java

public interface Measurable {
    double area();
    String name();

    default String describe() {          // relies on name() and area(): works for every shape
        return name() + " with area " + String.format("%.2f", area());
    }
}

Rectangle.java

public class Rectangle implements Measurable {
    private final double width, height;

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

    @Override public double area() { return width * height; }
    @Override public String name() { return "Rectangle"; }
}

Circle.java

public class Circle implements Measurable {
    private final double radius;

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

    @Override public double area() { return Math.PI * radius * radius; }
    @Override public String name() { return "Circle"; }
}

Square.java

public class Square implements Measurable {
    private final double side;

    public Square(double side) { this.side = side; }

    @Override public double area() { return side * side; }
    @Override public String name() { return "Square"; }
}

Shapes.java

public class Shapes {

    static double totalArea(Measurable[] shapes) {
        double total = 0;
        for (Measurable m : shapes) {
            total += m.area();
        }
        return total;
    }

    static Measurable largest(Measurable[] shapes) {
        Measurable largest = shapes[0];   // start from the first one and keep comparing
        for (Measurable m : shapes) {
            if (m.area() > largest.area()) {
                largest = m;
            }
        }
        return largest;
    }

    public static void main(String[] args) {
        Measurable[] shapes = { new Rectangle(3, 4), new Circle(2), new Square(3.5) };

        for (Measurable m : shapes) {
            System.out.println(m.describe());
        }
        System.out.printf("Total area: %.2f%n", totalArea(shapes));
        System.out.println("Largest: " + largest(shapes).describe());
    }
}

Why it’s written this way:

  • describe() is default because it doesn’t depend on any particular shape: it uses name() and area(), which belong to the contract. All three shapes get it for free.
  • The totalArea and largest methods take a Measurable[]. If tomorrow you add a Triangle, you don’t have to touch them.
  • largest starts from shapes[0]. It’s safer than starting from 0 or from some made-up value, because the starting shape is one of the real ones.
  • The arithmetic checks out: 12.00 + 12.57 + 12.25 = 36.82 (the circle is π · 2² = 12.566…, which rounds to 12.57).

Level 2 — Solution

Resource.java, Lendable.java and Bookable.java

public interface Resource {
    String name();
}
public interface Lendable extends Resource {   // inherits name() from Resource
    boolean lend(String member);
    void giveBack();
    boolean available();

    default String status() {
        return name() + ": " + (available() ? "available" : "on loan");
    }
}
public interface Bookable extends Resource {
    boolean book(String member, String day);
    String bookedBy(String day);   // null if the day is free

    default boolean isFree(String day) {   // relies on bookedBy
        return bookedBy(day) == null;
    }
}

Book.java

public class Book implements Lendable {
    private final String title;
    private String lentTo;

    public Book(String title) { this.title = title; }

    @Override public String name() { return title; }
    @Override public boolean available() { return lentTo == null; }

    @Override
    public boolean lend(String member) {
        if (!available()) return false;
        lentTo = member;
        return true;
    }

    @Override public void giveBack() { lentTo = null; }
}

Room.java

public class Room implements Bookable {
    private static final String[] DAYS = { "Monday", "Tuesday", "Wednesday", "Thursday", "Friday" };
    private final String name;
    private final String[] bookings = new String[DAYS.length];

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

    @Override public String name() { return name; }

    private int indexOf(String day) {
        for (int i = 0; i < DAYS.length; i++) {
            if (DAYS[i].equals(day)) return i;
        }
        return -1;
    }

    @Override
    public boolean book(String member, String day) {
        int i = indexOf(day);
        if (i == -1 || bookings[i] != null) return false;
        bookings[i] = member;
        return true;
    }

    @Override
    public String bookedBy(String day) {
        int i = indexOf(day);
        return i == -1 ? null : bookings[i];
    }
}

Laptop.java

public class Laptop implements Lendable, Bookable {   // two capabilities in one class
    private static final String[] DAYS = { "Monday", "Tuesday", "Wednesday", "Thursday", "Friday" };
    private final String name;
    private String lentTo;
    private final String[] bookings = new String[DAYS.length];

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

    @Override public String name() { return name; }
    @Override public boolean available() { return lentTo == null; }

    @Override
    public boolean lend(String member) {
        if (!available()) return false;
        lentTo = member;
        return true;
    }

    @Override public void giveBack() { lentTo = null; }

    private int indexOf(String day) {
        for (int i = 0; i < DAYS.length; i++) {
            if (DAYS[i].equals(day)) return i;
        }
        return -1;
    }

    @Override
    public boolean book(String member, String day) {
        int i = indexOf(day);
        if (i == -1 || bookings[i] != null) return false;
        bookings[i] = member;
        return true;
    }

    @Override
    public String bookedBy(String day) {
        int i = indexOf(day);
        return i == -1 ? null : bookings[i];
    }
}

Library.java

public class Library {

    public static void main(String[] args) {
        Resource[] resources = {
            new Book("Java for FP2"),
            new Room("Room 3"),
            new Laptop("Laptop 07")
        };

        // Ana borrows everything that can be lent
        for (Resource r : resources) {
            if (r instanceof Lendable p) {   // only if it is lendable
                System.out.println("Ana borrows " + r.name() + ": " + (p.lend("Ana") ? "OK" : "unavailable"));
            }
        }
        // Luis tries the book
        Lendable book = (Lendable) resources[0];
        System.out.println("Luis borrows " + book.name() + ": " + (book.lend("Luis") ? "OK" : "unavailable"));

        // Bookings
        Bookable room = (Bookable) resources[1];
        System.out.println("Luis books " + room.name() + " on Monday: " + (room.book("Luis", "Monday") ? "OK" : "taken"));
        System.out.println("Marta books " + room.name() + " on Monday: " + (room.book("Marta", "Monday") ? "OK" : "taken"));
        Bookable laptop = (Bookable) resources[2];
        System.out.println("Marta books " + laptop.name() + " on Tuesday: " + (laptop.book("Marta", "Tuesday") ? "OK" : "taken"));

        // Status
        System.out.println("--- Status ---");
        for (Resource r : resources) {
            if (r instanceof Lendable p) {   // only if it is lendable
                System.out.println(p.status());
            }
            if (r instanceof Bookable b) {   // only if it is bookable
                System.out.println(r.name() + ": Monday: " + (b.isFree("Monday") ? "free" : "booked by " + b.bookedBy("Monday"))
                        + ", Tuesday: " + (b.isFree("Tuesday") ? "free" : "booked by " + b.bookedBy("Tuesday")));
            }
        }
    }
}

Why it’s written this way:

  • Lendable and Bookable extend Resource. That way the array can be of type Resource (everything has a name), and any class that implements one of the two is forced to have name().
  • Laptop implements both interfaces: that’s why it answers yes to both instanceof checks and shows up twice in the status. There’s no default conflict: status() belongs only to Lendable and isFree() belongs only to Bookable.
  • bookedBy returns null for a free day and isFree() builds on it. It’s a nice example of a default that uses the contract to build something new.
  • Room and Laptop repeat the booking code. That’s on purpose, to keep the focus on interfaces. If you wanted to avoid it, you could move that part into a helper class and reuse it.

Level 3 — Solution

Sensor.java, Calibratable.java and Alarmable.java

public interface Sensor {
    String id();
    String unit();
    double read();

    static String format(double value, String unit) {   // called as Sensor.format(...)
        return String.format("%.1f %s", value, unit);
    }

    default String summary() {
        return id() + " (" + unit() + ")";
    }
}
public interface Calibratable extends Sensor {
    void calibrate(double offset);

    @Override
    default String summary() {
        return Sensor.super.summary() + ", calibratable";
    }
}
public interface Alarmable extends Sensor {
    double threshold();

    default boolean inAlarm() {
        return read() > threshold();
    }

    @Override
    default String summary() {
        return Sensor.super.summary() + ", alarm > " + Sensor.format(threshold(), unit());
    }
}

Thermometer.java

public class Thermometer implements Calibratable, Alarmable {
    private final String id;
    private double reading;
    private final double threshold;

    public Thermometer(String id, double reading, double threshold) {
        this.id = id;
        this.reading = reading;
        this.threshold = threshold;
    }

    @Override public String id() { return id; }
    @Override public String unit() { return "°C"; }
    @Override public double read() { return reading; }
    @Override public double threshold() { return threshold; }
    @Override public void calibrate(double offset) { reading += offset; }

    // Two interfaces bring their own summary(): we must decide which one wins
    @Override
    public String summary() {
        return Alarmable.super.summary() + ", calibratable";
    }
}

Humidity.java and Pressure.java

public class Humidity implements Sensor {
    private final String id;
    private final double reading;

    public Humidity(String id, double reading) {
        this.id = id;
        this.reading = reading;
    }

    @Override public String id() { return id; }
    @Override public String unit() { return "%"; }
    @Override public double read() { return reading; }
}
public class Pressure implements Alarmable {
    private final String id;
    private final double reading;
    private final double threshold;

    public Pressure(String id, double reading, double threshold) {
        this.id = id;
        this.reading = reading;
        this.threshold = threshold;
    }

    @Override public String id() { return id; }
    @Override public String unit() { return "hPa"; }
    @Override public double read() { return reading; }
    @Override public double threshold() { return threshold; }
}

Station.java

public class Station {

    static void report(Sensor[] sensors, String title) {
        System.out.println("=== " + title + " ===");
        int alarms = 0;
        for (Sensor s : sensors) {
            String line = s.summary() + " -> " + Sensor.format(s.read(), s.unit());
            if (s instanceof Alarmable a && a.inAlarm()) {   // does it have the capability? is it active?
                line += "  ⚠ ALARM";
                alarms++;
            }
            System.out.println(line);
        }
        System.out.println("Alarms: " + alarms);
    }

    public static void main(String[] args) {
        Sensor[] sensors = {
            new Thermometer("T1", 27.5, 30),
            new Thermometer("T2", 32.1, 30),
            new Humidity("H1", 61.0),
            new Pressure("P1", 1013.2, 1020)
        };

        report(sensors, "Initial report");

        for (Sensor s : sensors) {
            if (s instanceof Calibratable c) {   // only the calibratable ones
                c.calibrate(3.0);
            }
        }

        report(sensors, "After calibrating");
    }
}

Why it’s written this way:

  • Sensor.super.summary() inside Calibratable and Alarmable lets each capability add its own part without repeating what they have in common.
  • Thermometer inherits two summary() methods and has to decide. Here we call Alarmable.super.summary() (which already includes T1 (°C) and the alarm) and add , calibratable by hand. We can’t call both versions, because the T1 (°C) prefix would come out twice.
  • Sensor.format(...) is a static method of the interface: you call it with the interface name, and both Alarmable and Station use it.
  • In report, s instanceof Alarmable a && a.inAlarm() asks about the capability and its state in a single condition. The humidity sensor (which isn’t Alarmable) never reaches the second half.
  • After calibrating, T1 goes from 27.5 to 30.5 and passes its threshold: the second report has two alarms.

Watch it step by step in Python Tutor

Python Tutor runs the code line by line and draws variables, objects and references. Choose Java. Here is snippet 1 already fixed, using the classic instanceof plus cast so every step is easy to see:

public class Tutor {
    interface Sellable { String name(); }

    static class Book implements Sellable {
        public String name() { return "Book"; }
        public String author() { return "Sergio"; }
    }

    static class TShirt implements Sellable {
        public String name() { return "T-shirt"; }
    }

    public static void main(String[] args) {
        Sellable[] catalog = { new Book(), new TShirt() };

        for (int i = 0; i < catalog.length; i++) {
            Sellable v = catalog[i];
            if (v instanceof Book) {
                Book l = (Book) v;
                System.out.println(l.name() + " by " + l.author());
            } else {
                System.out.println(v.name() + " (no author)");
            }
        }
    }
}

Expected output:

Book by Sergio
T-shirt (no author)

What to watch, step by step:

  1. In the catalog array there are two different objects: a Book and a TShirt. Both are stored as Sellable, but each one keeps its own class.
  2. On the first turn, v instanceof Book is true. Then Book l = (Book) v creates a variable l that points to the same object as v: nothing gets copied, there’s just one more arrow.
  3. On the second turn, v points to the TShirt. The condition is false and the block is skipped: the cast doesn’t even run.
  4. Now change the program: delete the if and leave only Book l = (Book) v;. When it reaches the second turn it should stop with a ClassCastException: that’s the bug from snippet 1, live.

Cheat sheet: interfaces in Java

The essentials in 30 seconds

  • An interface is a contract: it says what can be done, not how.
  • You fulfil it with implements, and you must override everything abstract, with public.
  • A class has one parent (extends) and many interfaces (implements A, B).
  • A variable of interface type can hold any object that fulfils it, but it only sees what the contract declares.
  • To use something specific to a class: instanceof first, conversion after.
  • default gives an optional body; static belongs to the interface; fields are constants.

Syntax

I want to…CodeResult
Declare a contractinterface Measurable { double area(); }public abstract method
Fulfil itclass Circle implements Measurable { public double area() {…} }public is mandatory
Several interfacesclass Laptop implements Lendable, BookableFulfils both contracts
Extend an interfaceinterface Lendable extends ResourceInherits its methods
Variable of interface typeMeasurable m = new Circle(2);No cast, upwards
Array of interface typeMeasurable[] shapes = {…};Mixes different classes
Ask about the capabilityif (r instanceof Lendable p) { p.lend(…); }Checks and converts
Classic castBook b = (Book) v;ClassCastException if it isn’t one
Method with a bodydefault String describe() {…}Optional to override
Choose in a conflictAlarmable.super.summary()Calls that version
Utility of the interfacestatic String format(…) {…}Sensor.format(…)
Constantint MAX = 3;public static final

Templates from the exercises

**A default that builds on the contract** (Level 1):

default String describe() {
    return name() + " with area " + String.format("%.2f", area());
}

Ask about the capability before using it (Level 2):

for (Resource r : resources) {
    if (r instanceof Lendable p) {
        p.lend("Ana");
    }
}

**Resolve a default conflict** (Level 3):

@Override
public String summary() {
    return Alarmable.super.summary() + ", calibratable";
}

The most common mistakes

SymptomLikely cause
“attempting to assign weaker access privileges; was public”Missing public when overriding an interface method
“is not abstract and does not override abstract method…”A method of the contract isn’t implemented
“Lendable is abstract; cannot be instantiated”You wrote new on an interface
“cannot find symbol” with an interface-type variableYou use a method of the class without instanceof + conversion
“incompatible types: X cannot be converted to Y”The downcast is missing
ClassCastExceptionA cast without checking the real type with instanceof
“inherits unrelated defaults for…”Two identical default methods: override and use Interface.super
“cannot assign a value to static final variable”You’re trying to change a constant of the interface

Before you hand it in, ask yourself

  1. Did I write **public** on every method that implements the interface?
  2. Do I ask with **instanceof** before any downcast?
  3. Does my code depend on the interface (Measurable, Lendable) and not on concrete classes?
  4. If two interfaces bring the same default, did I decide which one wins?

If you want to review the topic from the ground up, go back to the theory of interfaces.

Keep goingAll Java (en) articles

Did this article help you? Here is how you can support SergioLearns →

Sigue aprendiendo

Leave a Reply

Your email address will not be published. Required fields are marked *