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.
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?
Reveal 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.
- Strokeable[] is the array type: an array whose base type is the interface Strokeable
listOfFiguresis 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.
- 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.
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.
- 1The loop visits each index from 0 up to the array length, minus one.
- 2At each index, the expression
listOfFigures[i] selects the slot's contents. - 3The call
stroke(g)is accepted because Strokeable declaresstroke(). - 4The 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.
- 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
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?
- 1Check the base type
The base type of
listOfFiguresis Strokeable, and Strokeable declaresstroke(). The compiler therefore accepts the call. The base type is not the problem. - 2Check 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.
- 3Attempt 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. - 4Locate the cause
The fault is in the slot's contents, not in the array's base type and not in any object's class.
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.
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 likelistOfFigures = new Strokeable[10]; followed immediately by a loop that calls stroke() on every slotWhy it failsnew 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 insteadAssign 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 likeWriting Line first and then assuming every later slot must also hold a Line
Why it failsThe 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 insteadLet 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 likelistOfFigures[0].resize() when Strokeable declares only stroke()Why it failsThe compiler checks calls against the base type. The base type Strokeable only guarantees the methods it declares.
Do this insteadCall 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
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.