Link Menu Search Expand Document

Arrays of objects; interfaces

ed Lesson

Table of contents


Order of evaluation

Consider the following code snippet.

int x = 5;
int y = 5;
int z = y;

We might visualize this by imagining having three boxes to store values (named x, y, z respectively), all assigned to the value 5.

Visualizing integer variables

It’s important to note z = y does not link the variables z and y. Assignment statements in Java follow very specific steps.

Assignment statement
  1. Evaluate the right hand side of the assignment. Since it’s a variable, use the value of y, 5.
  2. Store the value in the variable on the left hand side of the assignment. Assign z to the value 5.
What happens when we evaluate y = y + 1?

y = y + 1 first evaluates the right hand side by using the current value of y (5), adds 1 to that value (6), and then assigns the result to y. Note that z doesn’t change during this assignment since only the value for y is reassigned.

Reference semantics

Consider the following Point class and the code snippet below. (Note the style issue of declaring public fields!)

Implementer

public class Point {
    public int x;
    public int y;

    public Point(int xValue, int yValue) {
        x = xValue;
        y = yValue;
    }
}

Client

Point p = new Point(1, 2);
Point q = new Point(1, 2);
Point r = q;

Many programmers picture this code incorrectly! The key misunderstanding here is the idea that the Point somehow fits inside the variable p.

Incorrect
Wrong Point example

p does not store the Point itself, but rather a reference to the Point instance.

The correct picture is shown below. Each Point instance in the program can be thought of as a person with their own phone number (shown in the picture in purple). The variables p, q, and r only store phone numbers, not the whole Point instance.

Correct
Correct Point example

When Java executes the assignment r = q, just as in the int example, it did not somehow make a link between the variables q and r. The steps for how this assignment statement works are exactly the same as they were with primitive variables, with the key difference that the value of q is a long phone number.

Summarize the previous paragraph in your own words.

When Java evaluates r = q, Java doesn’t copy the object but rather the phone number.

Arrays of objects

When we create a new array, it initializes all the values to the default value. For numeric types like int and double, the default value is 0.

Client

int[] nums = new int[5];
System.out.println(Arrays.toString(nums)); // [0, 0, 0, 0, 0]

What about an array of Point objects?

Client

Point[] points = new Point[5];
System.out.println(Arrays.toString(points)); // [null, null, null, null, null]

The default value for arrays of objects (such as Point[]) is a special placeholder value known as null.

null value

Some programmers might consider null as an object that doesn’t exist. A better way of thinking about it using the phone number analogy is that null represents a special, reserved phone number (maybe call it the number (000) 000-0000) that no person can have. This special phone number has no associated object and it can’t be called.

According to this interpretation, a Java variable can store the value null. The problem comes up when we try to use the null value.

Client

points[0].x = 3;

Following the order of evaluation for assignment statements, we evaluate the right hand side to get the number 3. Then, in order to determine where to assign that value, we need to find the Point instance stored in the array index points[0]. However, points[0] is null.

The issue arises when we try to set the x field for null. The program crashes with a NullPointerException. Java can’t “call the phone number” for null! There’s no actual Point object there, so it’s not possible to set the x field of something that doesn’t exist.

In order to address this issue, we can initialize the array by creating new points.

Client

for (int i = 0; i < points.length; i++) {
    points[i] = new Point(0, 0);
}

Now the array will store references to 5 new Point objects that we can manipulate!

Syntactic sugar

Let’s return to the Shakespeare example. A couple lessons ago, we wrote a method countUnique that used a Set to store all of the unique words in a Scanner.

Client

public static int countUnique(Scanner input) {
    Set<String> words = new HashSet<String>();
    while (input.hasNext()) {
        String word = input.next();
        words.add(word);
    }
    return words.size();
}

If we print out the words in the set, it turns out that there’s a lot of near-duplicates. Here’s all the strings in the words set related to “conceit”.

  • Conceit
  • conceit
  • conceit’s
  • conceited
  • conceitless
  • conceits

What if we now want to remove certain words from the Set? Let’s start by removing uppercase variants of words. We can outline the steps for a program to solve this problem.

  1. Loop over the set of words.
  2. If a word has any uppercase characters, then remove it from the set.

Since sets don’t keep track of element indices, there’s no get(int index) method! We can try to use a for-each loop instead.

Client

public static int countUnique(Scanner input) {
    Set<String> words = new HashSet<String>();
    while (input.hasNext()) {
        String word = input.next();
        words.add(word);
    }

    for (String word : words) {
        if (...) {
            words.remove(word);
        }
    }

    return words.size();
}

It turns out that this code will compile but crash with a ConcurrentModificationException on the call to words.remove(word) because the for-each loop is read-only. Java doesn’t allow elements to be removed from the underlying data structure during a for-each loop.

There is a way to solve this problem in Java by understanding how for-each loops work under the hood. For-each loops are an example of “syntactic sugar”: a bit of convenient syntax hiding more complicated behavior. We’ve been using other kinds of syntactic sugar in our programming. For example, whenever we say, i++, we really mean to say, i = i + 1. i++ is the syntactic sugar that buys the programmer some convenience.

Iterators

Just as Java translates i++ to i = i + 1, Java also translates for-each loops into iterators.

Client

for (String word : words) {
    // Body of for-each loop
}

The above for-each loop translates into the following iterator and while loop.

Client

Iterator<String> iter = words.iterator();
while (iter.hasNext()) {
    String word = iter.next();
    // Body of for-each loop
}

An Iterator is a lot like a Scanner: if there are any more elements (hasNext), get it for me (next). The iterator is attached to the data structure and returns the next item in the data structure. All collections (including all List and Set implementations) have a method iterator() that returns a new Iterator instance.

The Iterator interface has three methods.

  • hasNext to return whether or not there are any more elements.
  • next to return the next element from the iterator.
  • remove to remove the last-returned element from the iterator.

Using the remove method, we can modify the underlying data structure. Rather than remove the items from the set of words directly (which will cause the iterator to crash the program!), we can instead use the iterator’s remove method. This way, the iterator isn’t surprised when items are removed from the underlying data structure.

An analogy for iterators is that they’re like pharmacists. Pharmacists (iterators) work together with pharmarcies (data structures) and are trained to handle prescription drugs carefully (data structure elements). In order to purchase prescription drugs, we (as users) have to work with the pharmacist to fulfill prescriptions. Pharmacists help users fulfill prescriptions by returning the specific drugs that they need.

As a style consideration, we typically prefer using the for-each loop over using iterators directly. The main use for iterators is when we need to remove from a for-each loop.