Concepts / Interface Type

Interface Type

An interface can be the base type of an array, as in Strokeable[] listOfFigures, and its elements can refer to any objects whose classes implement the interface.

  • Programming

One Array for Many Kinds of Figures

Suppose a drawing program keeps a list of figures on screen. Some of the figures are lines and some are circles, and the program needs to draw all of them. One approach is to create one array for lines and another array for circles. The problem with that approach is that every drawing step would need to know which kind of figure it was touching. This article shows a different approach: an interface can serve as the base type of an array, so one array can hold many different kinds of objects.

What do you think happens?

Before reading on, decide: can a single array hold both a Line and a Circle, if both classes implement the same interface?

  • Yes, the array can hold both

  • No, an array can only hold one class

Reveal answer
Answer:

Yes, the array can hold both

The base type of the array is the interface, not the concrete class. Line and Circle both implement Strokeable, so each of them is a legal element of a Strokeable array.

Declaring the Array and Its Empty Slots

An interface can be the base type of an array. The source uses the interface Strokeable as its example. The array type Strokeable[] is used to declare a variable, and a new array of that type is created with new Strokeable[10]. Declaration and creation are two separate steps. First, a variable named listOfFigures is declared with type Strokeable[]. Then an array of ten elements is created and assigned to it.

java
Notes on the code
  • Strokeable[] is the array type: an array whose base type is the interface Strokeable
  • listOfFigures is the variable that will refer to the array
  • new Strokeable[10] allocates ten slots, numbered 0 through 9

Creating the array allocates its ten slots, but it does not put any objects into them. A slot refers to an object only after an assignment has stored one there. Until that happens, the slot holds no object. This matters when you read from the array, because reading an unassigned slot does not give you a figure.

Filling Slots With Different Classes

The base type is what the compiler uses to decide what each element is allowed to be. Because the base type is Strokeable, the array is a container of Strokeable references, not a container of Line or Circle references. The concrete class of each object is a separate question, and it becomes important when the elements are filled in.

java
Notes on the code
  • Slot 0 receives a Line
  • Slot 1 receives a Circle
  • Slot 2 receives another Line
  • Constructor arguments are omitted here because the source does not specify them

Each of these assignments is legal because Line and Circle both implement Strokeable. The array does not record a single class for all of its elements. Each element can refer to a different class, as long as that class implements the interface. The diagram below shows the relationship: the interface sits above the implementing classes, and each filled slot refers to an object of one of them.

StrokeableLineCirclelistOfFiguresbase type Strokeableslot 0Line objectslot 1Circle objectslot 2Line objectimplementsimplementsholdsholdsholdsrefers torefers torefers to
How can one array of type Strokeable[] hold Line and Circle objects at once, and how do the filled slots relate to the classes that implement the interface?

Why Every Element Has stroke()

The reason for using an interface as the base type is the methods it guarantees. Every element of the array refers to an object whose class implements Strokeable, so every element has a stroke() method. This is what lets you write a single statement such as listOfFigures[i].stroke(g) and have it work for any element, whether that element is a Line or a Circle.

java
Walk through it
  1. 1
    The loop visits each index from 0 up to the array length, minus one.
  2. 2
    At each index, the expression listOfFigures[i] selects the slot's contents.
  3. 3
    The call stroke(g) is accepted because Strokeable declares stroke().
  4. 4
    The method that actually runs is the one in the class of the object stored in that slot, which is the polymorphism described in the source.
Notes on the code
  • The loop does not check which class each element belongs to
  • The call stroke(g) is checked by the compiler against the base type Strokeable
  • At run time, each element runs the stroke() method of its own class

This behavior is polymorphism. The same call produces behavior that depends on the actual class of the object in the slot. The code that makes the call does not need to know which class it is dealing with. It only needs to know that the element has the methods declared by the interface. The loop above assumes that every slot has been filled; the next section shows what happens when one has not.

Tracing a Call That Fails

Worked example

Finding where listOfFigures[3].stroke(g) fails

The array was created with new Strokeable[10]. Slots 0 through 2 were assigned, but slot 3 was never assigned. A program calls listOfFigures[3].stroke(g). Where does the failure happen, and why?

  1. 1
    Check the base type

    The base type of listOfFigures is Strokeable, and Strokeable declares stroke(). The compiler therefore accepts the call. The base type is not the problem.

  2. 2
    Check the slot contents

    Slot 3 was created but never assigned. A slot refers to an object only after an assignment has stored one there, so slot 3 holds no object.

  3. 3
    Attempt the call

    The call needs an object to run stroke() on. Slot 3 supplies none, so the call cannot succeed. The failure happens when the program runs, not when it compiles, because the compiler only checks the call against the base type.

  4. 4
    Locate the cause

    The fault is in the slot's contents, not in the array's base type and not in any object's class.

Result

The call fails at slot 3 because that slot holds no object. The fix is to make sure every slot is assigned before the loop reaches it.

slot 0Line objectslot 1Circle objectslot 2Line objectslot 3empty
When listOfFigures[3].stroke(g) fails, is the problem the base type, the slot contents, or the object's class, and at which step does it happen?

Read the diagram from left to right. Slots 0 to 2 hold objects, so calls on them have something to run. Slot 3 is the only changed slot, and it is empty. The base type is the same Strokeable for every slot, so the base type cannot explain the difference between slot 2 and slot 3. The contents of the slot explain it.

Mistakes Beginners Make With Interface Arrays

The following mistakes come up often when people first use an array whose base type is an interface. Each one is tied to a rule described above.

  • Common mistake

    Believing that creating the array creates the figures

    What it looks like
    listOfFigures = new Strokeable[10]; followed immediately by a loop that calls stroke() on every slot
    Why it fails

    new Strokeable[10] allocates ten slots, but it does not put any objects into them. Until an assignment stores an object, the slot holds no object.

    Do this instead

    Assign an object to each slot before calling methods on it, or check that the slot has been filled.

  • Common mistake

    Assuming all elements must be the same class

    What it looks like

    Writing Line first and then assuming every later slot must also hold a Line

    Why it fails

    The array does not record one class for all its elements. Each element can refer to a different class, as long as that class implements Strokeable.

    Do this instead

    Let each slot refer to any object whose class implements the interface, such as a Circle in slot 1.

  • Common mistake

    Calling a method the interface does not declare

    What it looks like
    listOfFigures[0].resize() when Strokeable declares only stroke()
    Why it fails

    The compiler checks calls against the base type. The base type Strokeable only guarantees the methods it declares.

    Do this instead

    Call only methods declared by Strokeable on elements of a Strokeable array.

  • Common mistake

    Forgetting that the loop variable must stay within the array bounds

Practice: Predicting the Failing Slot

EASY

An array Strokeable[] listOfFigures = new Strokeable[5] has slots 0 and 1 assigned a Line and a Circle, slot 2 assigned a Line, and slots 3 and 4 never assigned. A loop calls listOfFigures[i].stroke(g) for every index from 0 to 4. Which index is the first one where the call fails, and what must be true of that slot for the call to succeed?

Hints
  • Check whether the call is accepted by the base type first, then check the contents of each slot in order.
  • The first slot that holds no object is where the call cannot succeed.

Key Takeaways

  • An interface can be the base type of an array, such as Strokeable[] listOfFigures, and each element can refer to any object whose class implements that interface.
  • Declaring the array and creating it are separate steps; creating new Strokeable[10] allocates ten slots but puts no objects in them.
  • Every element has the methods the interface declares, so listOfFigures[i].stroke(g) works for any element, which is polymorphism.
  • When a call on an element fails, check the base type first, then check whether the slot actually holds an object.