Declaring and Creating Arrays
Declaring Student[] classlist and creating it with new Student[30] makes an array of references, not thirty Student objects.
One Variable for a Whole Class
Suppose a teacher wants a program that tracks thirty students in one class. Writing thirty separate variables, one per student, would be tedious, and it would make it hard to process the whole class with a single loop. An array solves this problem. An array stores many values of one type under a single name, and each value is reached through a numeric index written in square brackets.
So far in this course the values stored in an array have usually been primitives such as int or double. An array of objects follows exactly the same pattern, except that each element is an object of a class you have written, such as Student. A class of thirty students can then be modeled as one variable holding thirty Student objects, and any one student can be reached by writing an index in square brackets.
The work in this topic has three parts: declaring the array, filling it with objects, and then using each object. Most errors in this kind of code come from skipping or misordering one of those three parts. This article treats them as separate steps and asks you to predict the state of the program at each one, starting with a question you can answer before reading on.
What do you think happens?
Right after new Student[30] runs, and before any loop has run, what does classlist[3] refer to?
Reveal answer
A slot that exists in the array but refers to no Student object yet
The array has thirty positions, so slot 3 exists. But at the moment of creation none of the thirty references points to a Student object. Commit to this idea now, because the rest of the article shows how it changes.
Declaring Is Not Creating
Declaring an array variable gives the variable a type so that it can refer to an array. It does not create the array. The declaration tells the compiler what kind of thing the variable may refer to, but it does not supply that thing. A variable of an array type still needs a value before it can be used, and in this case the value has to be an array. The array must be created separately, using a special syntax.
The first statement in the Student example declares a variable named classlist that can refer to an array of Student objects. The second statement creates an array with thirty positions and makes classlist refer to it. These are two separate steps, and the first one alone leaves the program with no array to work with.
Student[] classlist;classlist has a type, Student[], but refers to no array. The array does not exist yet.
Student[] classlist;
classlist = new Student[30];classlist refers to an array of length 30. That array is a container of 30 references, each still empty.
The before and after states differ in one important way. The variable exists in both, but only the second state has an array for it to refer to. Any code that uses classlist between these two statements is working with a variable that has no array yet, and that is the first place an unexpected result can begin.
Building the Array Type from a Base 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. So the base type Student becomes the array type Student[], and the base type int becomes int[].
Read Student[] classlist; from left to right. Student is the base type, meaning the kind of element the array holds. The empty brackets [] carry the array marker. classlist is the name you will later use to reach the array.
Once you have the type, a variable is declared by writing the type, a space, and the variable name, followed by a semicolon. The same pattern applies to every base type. The table below lists the examples from the source and one further case of the same pattern.
| Base type | Array type | Example declaration |
|---|---|---|
| int | int[] | int[] A; |
| String | String[] | String[] names; |
| Student | Student[] | Student[] classlist; |
Each declaration names a variable that may refer to an array. None of these lines creates an array.
Creating the Array with new
Creating an array is the step that produces the actual array. In Java this is done with the operator new. The source notes that arrays in Java are objects, and 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.
Read the second line from left to right. The array variable A is on the left of the assignment, so it receives the new array as its value. On the right, new is followed by the base type int and then a length in square brackets. The length is required, because creating an array means deciding how many elements it has. Here the length is 5, so A refers to an array of five elements.
Creating a names array with a different base type
Declare a variable that can refer to an array of twelve Strings, then create that array.
- 1Find the base type
The elements are Strings, so the base type is String.
- 2Build the array type
Add an empty pair of brackets to the base type, giving String[].
- 3Declare the variable
Write the type, a space, a name, and a semicolon: String[] names;. The variable can now refer to an array, but no array exists yet.
- 4Create the array
Write names = new String[12]; The base type String and the length 12 are both required. Now names refers to an array of twelve positions.
- 5Check the order
The creation line must come before any line that reads or writes names[0] through names[11], because the variable has no array to use until it is assigned one.
String[] names; names = new String[12]; gives a variable that refers to an array of twelve String positions.
When you read a line that declares an array variable, look at the lines that follow for the creation statement. An array variable must be assigned an array before it is used, so creation must come before any work on the array's elements.
Tracing the Array Right After Creation
Return to the question you were asked at the start. Right after new Student[30] runs, the array holds thirty positions, and each position is a reference to a Student object. At this stage none of those references points to a Student yet. The diagram shows the first four of the thirty positions. The same structure holds for every index from 0 to 29 once the loop has completed.
The array itself is only the set of these positions. Each Student object lives separately, and a slot only gets to refer to one when your code assigns it. That is why the array created by new Student[30] is described as an array of references rather than thirty Student objects. The source makes the same point: at this stage none of the references points to a Student yet.
Filling Each Slot with a Loop
Because each element must be initialized separately, the usual pattern is a loop that runs over every index and stores a new object in each position. The declaration and creation come first, and the loop follows. The loop variable i runs from 0 up to, but not including, 30, so it visits each valid index exactly once. The valid indexes of a thirty-element array are 0 through 29.
The loop starts with i equal to 0 and continues while i is less than 30. On each pass, new Student() creates one Student object, and the assignment stores a reference to it in position i. The increment i++ moves to the next index. When i reaches 30 the condition fails and the loop stops, so every slot from 0 to 29 has been given an object.- 1for (int i = 0; i < 30; i++) starts the counter at 0, repeats while i is below 30, and adds 1 after each pass.
- 2new
Student()creates one new Student object on each pass. - 3classlist[i] = ... stores a reference to that new object in the slot at index i.
- 4When the loop ends, every index from 0 through 29 refers to its own Student object.
Notice what the diagram implies. Until the pass that handles a given index has run, that slot is still empty. A read of that slot at that moment does not reach a Student. The loop is therefore not a formality: the program is only safe to use the array once the loop has finished.
Using an Object Through Its Index
After initialization, classlist[i] behaves like any other variable of type Student. The index selects which object to work with, and the dot selects a field or method of that object. The source gives two examples. To read the name of student number 3, write classlist[3].name. To compute the average for student number i, call classlist[i].getAverage().
Read classlist[3].name from left to right. classlist[3] first selects the slot at index 3, which yields a reference. The dot then follows that reference into the Student object and reads its name. Each step depends on the one before it. If the slot at index 3 were still empty, the expression would fail at its first step, and the name field would never be reached.
Where Array-of-Objects Code Goes Wrong
Common mistake Assuming that declaring the array creates it
What it looks likeStudent[] classlist; then classlist[0].name is read straight away
Why it failsThe declaration gives classlist a type only. No array exists yet, so there is no slot 0 to use.
Do this insteadWrite classlist = new Student[30]; before any line that works with the elements.
Common mistake Assuming that creating the array creates the Student objects
What it looks likeclasslist = new Student[30]; then classlist[3].getAverage() is called with no loopWhy it failsnew Student[30] creates thirty references, and none of them points to a Student yet. Slot 3 exists but has no object behind it.
Do this insteadRun a loop that stores new Student() in every index from 0 through 29 before using the slots.
Common mistake Using an off-by-one loop bound
What it looks likefor (int i = 0; i <= 30; i++) with the same bodyWhy it failsThe valid indexes of a thirty-element array are 0 through 29. Index 30 is outside the array.
Do this insteadUse i < 30, so the loop visits each valid index exactly once.
Common mistake Forgetting that each element must be initialized separately, so that only the first slot gets an object
Common mistake Reading a slot before the loop has reached it
Why it failsEach slot is filled only on its own pass. Reading an index earlier in the program than its pass runs finds an empty slot.
Do this insteadKeep all reads of the array after the initialization loop has finished.
Predict the State at Each Step
Trace the following three steps in order and, before you read further, state what classlist refers to and what classlist[0] refers to after each step. Step 1: Student[] classlist; Step 2: classlist = new Student[3]; Step 3: for (int i = 0; i < 3; i++) { classlist[i] = new Student(); }. Then say what changes if the loop in step 3 is skipped.
Hints
- After step 1, the variable has a type but refers to no array.
- After step 2, the variable refers to an array of three slots, but none of them holds a Student object yet.
- After step 3, every index from 0 through 2 holds a reference to a new Student object. If the loop is skipped, classlist[1].name would be used on a slot that has no object behind it.
Keeping the Three Steps Apart
- 1Declaring Student[] classlist gives the variable a type so that it can refer to an array. It does not create the array.
- 2new Student[30] creates an array of thirty references. None of them refers to a Student object until your code assigns one.
- 3Each element must be initialized separately, usually with a loop from index 0 through 29 that stores new
Student()at each position. - 4After initialization, classlist[i] works like any other Student variable: classlist[3].name reads a field and classlist[i].getAverage() calls a method.
- 5When a result is wrong, check the declaration, then the loop bounds, then whether each index has been assigned an object.
Key Takeaways
- Declaring an array variable gives it a type so that it can refer to an array, but it does not create the array.
- new Student[30] creates an array of thirty references, and at that point none of them points to a Student object.
- Each element of an array of objects must be initialized separately, usually with a loop over indexes 0 through 29.
- Once initialized, classlist[i] can be used like any other Student variable, so the index selects the object and the dot reaches its fields or methods.
- When a result is wrong, check the declaration, the loop bounds, and whether each index has been assigned an object, in that order.