Concepts / Declaring and Creating Arrays

Declaring and Creating Arrays

Declaring Student[] classlist and creating it with new Student[30] makes an array of references, not thirty Student objects.

  • Programming

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?

  • A Student object with its fields already set

  • A slot that exists in the array but refers to no Student object yet

  • Nothing, because the array has no slot 3

Reveal answer
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.

java
Before
Student[] classlist;

classlist has a type, Student[], but refers to no array. The array does not exist yet.

After
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.

classlistdeclared as Student[]arraydoes not exist yetclasslistrefers to an arrayStudent array30 empty slotsrefers to
After Student[] classlist is declared but before new runs, what does classlist refer to, and how does that differ from after new Student[30] runs?

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 typeArray typeExample declaration
intint[]int[] A;
StringString[]String[] names;
StudentStudent[]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.

java

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.

Worked example

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.

  1. 1
    Find the base type

    The elements are Strings, so the base type is String.

  2. 2
    Build the array type

    Add an empty pair of brackets to the base type, giving String[].

  3. 3
    Declare 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.

  4. 4
    Create 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.

  5. 5
    Check 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.

Result

String[] names; names = new String[12]; gives a variable that refers to an array of twelve String positions.

Best practice

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.

classlistvariable of type Student[]Student arraylength 30Slot 0no Student yetSlot 1no Student yetSlot 2no Student yetSlot 3no Student yetrefers toholdsholdsholdsholds
After new Student[30] runs but before any loop, are there 30 Student objects, or 30 empty slots that can each refer to a Student? Slots 0 to 3 of the 30 are shown.

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.

java
Code walkthrough
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.
  1. 1
    for (int i = 0; i < 30; i++) starts the counter at 0, repeats while i is below 30, and adds 1 after each pass.
  2. 2
    new Student() creates one new Student object on each pass.
  3. 3
    classlist[i] = ... stores a reference to that new object in the slot at index i.
  4. 4
    When the loop ends, every index from 0 through 29 refers to its own Student object.
Startall 30 slots emptyi = 0slot 0 gets a Studenti = 1slot 1 gets a Studenti = 2slot 2 gets a Studenti = 29slot 29 gets a StudentLoop endsi reaches 30, all fillednext passnext passnext passand so oncondition fails
As the loop runs, how does each slot change from empty to a reference to a new object, and at which step would a slot still be empty if it were used too early?

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().

java

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 like

    Student[] classlist; then classlist[0].name is read straight away

    Why it fails

    The declaration gives classlist a type only. No array exists yet, so there is no slot 0 to use.

    Do this instead

    Write 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 like
    classlist = new Student[30]; then classlist[3].getAverage() is called with no loop
    Why it fails

    new 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 instead

    Run 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 like
    for (int i = 0; i <= 30; i++) with the same body
    Why it fails

    The valid indexes of a thirty-element array are 0 through 29. Index 30 is outside the array.

    Do this instead

    Use 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 fails

    Each 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 instead

    Keep all reads of the array after the initialization loop has finished.

Predict the State at Each Step

MEDIUM

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

  1. 1
    Declaring Student[] classlist gives the variable a type so that it can refer to an array. It does not create the array.
  2. 2
    new Student[30] creates an array of thirty references. None of them refers to a Student object until your code assigns one.
  3. 3
    Each element must be initialized separately, usually with a loop from index 0 through 29 that stores new Student() at each position.
  4. 4
    After initialization, classlist[i] works like any other Student variable: classlist[3].name reads a field and classlist[i].getAverage() calls a method.
  5. 5
    When 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.