Java from the ground up

Interfaces and a tiny hierarchy

A promise a class makes to the rest of the program, a class that extends another, and the polymorphism that follows from both.

Part 10 of 12

Advanced18 min readBack to Courses

An interface is a promise

An interface lists methods a class agrees to provide, with no code in them: interface Measured { double size(); } says that anything Measured can report its size. A class writes implements Measured and then has to supply that method, or the file will not compile.

The promise is what lets the rest of the program stop caring which class it is holding. A loop over Measured[] items can call size() on every entry, whether it is a box, a circle or something written next year.

extends: a class built on another class

class BigBox extends Box builds on an existing class. The new class starts with everything the old one had and may replace any of it. super(side) in the constructor runs the parent constructor first, so the fields the parent owns are filled before the child adds its own behaviour.

protected sits between public and private: the field stays hidden from the outside world but the subclasses may use it. That is exactly what BigBox needs to read side while working out a doubled size.

Polymorphism, and toString

Calling size() on a Measured variable does not decide anything at compile time. Java looks at the object that is really there when the program runs, and runs that class method. One call, several possible behaviours, chosen by the object rather than by the code.

toString() is the method every object already has, and printing an object calls it. Overriding it in each class is what turns System.out.println(item) into a readable line instead of a type name and an address.

The contract, and who checks it

An interface is a type made of promises. When a class says implements Measured, the compiler checks that every promised method has a body with a compatible signature. A variable of the interface type can then hold any object that keeps the promise.

An interface cannot be built with new. It has no constructor and no fields of its own, because it describes behaviour rather than state. The object always comes from a class. An interface may also carry a default method with a body, which is how a new method can be added to an old interface without breaking every class that already implements it: a class that says nothing inherits the default, and a class that needs different behaviour overrides it.

Inheritance is an is-a relationship

extends says the child is a kind of the parent. A BigBox is a Box, so it can be used anywhere a Box is expected. The reverse is not true: a Box is not necessarily a BigBox, and assigning one to the other needs a cast that may fail at run time.

A class has one parent, and may implement several interfaces. That is the usual shape: one line of inherited state, several promises about behaviour. @Override is optional, but writing it lets the compiler catch a misspelled method that was meant to replace one.

Checking before you cast

An interface variable can hold any object that keeps the promise, and sometimes you need to know which class is really there. instanceof answers that question: if (item instanceof Box) is true only when the object is a box or something built on one. Only then is a cast safe, and only then does the cast do anything useful.

A cast that is wrong does not fail quietly. (Box) item on a circle throws ClassCastException at that line, which is better than corrupting a value but worse than asking first. The habit is to test and cast together, in one branch, so the compiler and the reader both see that the cast is guarded. If you find yourself casting in many places, the interface is probably missing a method that would make the cast unnecessary.

One promise, two classes, one loop

interface Named {
    String label();
}

class Cat implements Named {
    public String label() { return "cat"; }
}

class Dog implements Named {
    public String label() { return "dog"; }

    // Extra behaviour is allowed on top of the promise.
    String sound() { return "woof"; }
}

Named[] pets = { new Cat(), new Dog() };
for (Named pet : pets) {
    System.out.println(pet.label());   // cat, then dog
}

A box, and a bigger box

Press Run and note that both lines came from the same printing statement. Then add a class that implements Measured in a new way, put one in the array, and run it again.

Output
Click "Run" to execute your code

Three inheritance mistakes: forgetting public on a method that implements an interface (the promise is public, so the implementation must be too), calling super(...) after other statements in a constructor, and hiding a field in the child when you meant to reuse the parent one. The first two stop compilation; the third gives you two fields with one name.

Try these next

  • Add a Triangle class that implements Measured, put one in the array, and check that the loop needs no change.
  • Override toString in Box and then in BigBox; call super.toString() from the child and print both versions.
  • Write an interface with two methods, implement it in one class, and try to leave one method out to read the compiler error.

A shape report with one loop

Press Run and check that the loop calls two different `area` methods without knowing which class it holds. Then add a third shape, put it in the array, and let the total update itself.

Output
Click "Run" to execute your code

Prefer the interface in the rest of your code: declare Measured and build a Box. Keeping the promise in view means the class behind it can be replaced without touching the loop that uses it.

Check what you learned

One question at a time. A wrong answer explains itself — take another go.

Part 1 of 5

What does an interface contain?