Arrays
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.
A Drawing Program With Mixed Figures
Imagine a drawing program that 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. That works, but every drawing step then has to know which kind of figure it is touching, and the code grows with every new kind of figure.
This article shows a different approach. An interface can serve as the base type of an array, so a single array can hold many different kinds of objects. Throughout the article the interface is Strokeable, and the array variable is named listOfFigures. The type of that variable is Strokeable[].
The plan for this article is to trace what happens to the array step by step: first the declaration, then the creation, then the filling of the slots, and finally the call that draws each figure. At each stage you will be asked what the variable refers to, so you can check your understanding against the rules before the explanation follows.
Predicting the Result of a Declaration
What do you think happens?
After the line Strokeable[] listOfFigures; runs, and before any new operator is used, does an array exist?
Reveal answer
No, the variable has a type but no array has been created
Declaring a variable gives it a type so that it can refer to an array, but the declaration does not supply that array. The array has to be created separately, using the new operator.
Forming an Array Type
An array type is built from two parts: the base type of the array, followed by a pair of empty square brackets. The base type is the type of each element the array will hold. The brackets mark the type as an array type rather than a single value of the base type.
Applying this pattern to the familiar examples gives String[] for an array of Strings and int[] for an array of ints. For the interface in this article, the base type is Strokeable, so the array type is Strokeable[]. The base type is never changed by the brackets; the brackets only say that the type describes a whole array of that base type.
- The base type String, followed by []: an array of Strings
- The base type int, followed by []: an array of ints
- The base type Strokeable, an interface, followed by []: an array whose elements are Strokeable references
Declaring the Array Variable
Once you have the type, a variable is declared by writing the type, a space, and the variable name, followed by a semicolon. The line Strokeable[] listOfFigures; is an example. The variable name is what you will later use to reach the array, and the type is what tells the compiler that the variable is allowed to refer to an array of Strokeable.
- Strokeable[] is the type, the base type followed by brackets
listOfFiguresis the name the program will use to reach the array- The semicolon ends the declaration; no array has been created yet
The source makes the key point directly: declaring a variable does not make the actual array. A variable of an array type still needs a value before it can be used, and that value has to be an array. The diagram below compares the variable just after the declaration with the variable after an array has been created and assigned to it.
The two steps are easy to confuse because they often appear on neighboring lines and both mention the same variable name. The declaration gives the variable a type. Creation makes the thing the variable will refer to. Keeping those two steps apart is the main idea of this topic.
Creating the Array With new
Creating an array is the step that produces the actual array. In Java this is done with an operator named new. The source notes that arrays in Java are actually objects, and that the special syntax is related to this, although the details of object creation are not needed at this stage. What matters here is the pattern you write.
- A is the array variable on the left, so it receives the new array as its value
- new is the operator that creates the array
- int is the base type, the type of each element
- 5 is the length, the number of elements the array will have
Read the pattern from left to right. The array variable is on the left of the assignment, so it receives the new array. On the right, new is followed by the base type and then a length in square brackets. The length is required, because creating an array means deciding how many elements it has. An array variable must be assigned an array before it is used, so creation must come before any work on the array's elements.
The diagram shows the slots created by new Strokeable[3]. Creating the array allocates its 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. The diagram is a logical picture of the indexed set of slots; the source does not describe how the slots are laid out in memory.
Creating a String Array of Length Four
Declare a variable named names that can refer to an array of Strings, then create an array of four Strings and assign it to that variable.
- 1Write the type
The base type is String, so the array type is String followed by empty brackets: String[].
- 2Declare the variable
Write the type, a space, and the name, then a semicolon: String[] names;. At this point names can refer to an array, but it does not refer to one yet.
- 3Create the array
Use new with the base type and a length of 4: new String[4]. This allocates four slots, indexed 0 through 3.
- 4Assign the array
Assign the new array to the variable: names = new String[4];. The variable now refers to the array.
The variable names refers to an array with four slots. Each slot is empty until a String is assigned to it.
Filling Slots With Different Classes
After the array is created, each slot is assigned separately. In the source's example, a Line is assigned to slot 0, a Circle to slot 1, and another Line to slot 2, and the same pattern continues for the rest of the array. Each assignment 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.
- Declaration and creation together: a Strokeable[] variable refers to a new array of ten empty slots
- Slot 0 receives a Line object; Line implements Strokeable, so the assignment is legal
- Slot 1 receives a Circle object; a different class can sit in the same array
- Slot 2 receives another Line; the array does not need every slot to hold the same class
The diagram shows the relationship the statements set up. The variable names one array, and each slot in that array refers to an object. The objects in slot 0 and slot 2 are both Line objects, while slot 1 refers to a Circle. Every slot is allowed to refer to any class that implements Strokeable, and the base type is what limits the choices to that interface.
Why the Same Call Works on Every Element
The reason for using an interface as the base type is the methods it guarantees. Every element of the array has a stroke() method, because every element refers to an object whose class implements Strokeable. This is what lets a single statement such as listOfFigures[i].stroke(g) work for any element, whether that element is a Line or a Circle. The compiler checks the call against the base type, Strokeable, which declares stroke().
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, so a loop can draw every figure in the array without a separate branch for each kind.
- The loop visits every slot by index
- Each element is called through the interface; the loop does not check the concrete class
- Each call runs the
stroke()method of whatever object the slot refers to
Mistakes Beginners Make With Array Declarations
Common mistake Treating the declaration as if it created the array
What it looks likeStrokeable[] listOfFigures; followed by listOfFigures[0] = new Line();Why it failsThe declaration only gives the variable a type. No array exists until new creates one, so there are no slots to assign to.
Do this insteadCreate the array first: listOfFigures = new Strokeable[10]; and only then assign to its slots.
Common mistake Assuming the base type records the class of each element
What it looks likeBelieving that because the array is Strokeable[], every slot must hold a Line.
Why it failsThe base type constrains the interface, not the concrete class. Each slot can hold a different class that implements Strokeable.
Do this insteadRead Strokeable[] as an array of references to any object whose class implements Strokeable.
Common mistake Calling a method on a slot that was never assigned
What it looks likeCreating new Strokeable[10] and immediately calling listOfFigures[0].stroke(g) before any assignment.
Why it failsA slot refers to an object only after an assignment has stored one there. An empty slot has no object for the call to run on.
Do this insteadAssign an object to the slot before calling its methods, or check that the slot is filled first.
Common mistake Leaving out the length when creating an array
What it looks likeWriting new Strokeable; instead of new Strokeable[10];
Why it failsCreating an array means deciding how many elements it has, and the length is required.
Do this insteadGive the base type followed by the length in square brackets: new Strokeable[10].
Common mistake Using the array variable in code that runs before the creation statement
When you read a line that declares an array variable, look at the lines that follow for the creation statement. If the variable is used before that statement, the program is working with a variable that has no array yet.
Practice Tracing a Declaration
Trace the following three lines. After each line, state whether listOfFigures refers to an array, how many slots the array has, and whether any slot holds an object. Then say which line would make a call on listOfFigures[0] fail if it were made right after that line.
Strokeable[] listOfFigures;
listOfFigures = new Strokeable[3];
listOfFigures[0] = new Line();Hints
- After the first line, the variable has a type but no array has been created.
- The second line creates an array with three slots, and a new array's slots start out with no object in them.
- A call on
listOfFigures[0] needs an object in slot 0, so check the state after the second line, before the third line assigns it.
Key Takeaways
- An array type is the base type followed by a pair of empty square brackets, so the array type for the interface Strokeable is Strokeable[].
- Declaring an array variable gives it a type so that it can refer to an array, but it does not create the array; the new operator, with a base type and a length, creates it.
- Creating an array allocates its slots, but a slot refers to an object only after an assignment stores one there, so unassigned slots hold no object.
- Because the base type is an interface, one array can hold objects of different classes that implement it, and every element has the methods the interface declares.
- A loop can call
listOfFigures[i].stroke(g) on every element without knowing its concrete class; this is polymorphism, and a failing call usually traces back to a slot that was never assigned.