Suggested reading: sections 9.5 and 9.10

More about object variables

I have mentioned previously that Java has two broad categories of data types, primitive types and object types. In today's lecture I want to point out some important differences in the behavior of these two types of variables.

Let's begin by looking at some simple examples of code involving primitive data types.

In the first example I am going to write some code that involves assigning one variable to another.

int a = 3;
int b = a;
b++;
System.out.println("a = " + a);
System.out.println("b = " + b);

The key statement to pay attention to in this code is the assignment statement

int b = a;

This statement takes the current value of a, which is 3, and assigns that value to a different variable, b. Obviously, this results in b also having the value of 3. If we subsequently increment b, that will affect only the value of b, and will not affect the value of a. The print statement will then report that the value of a is 3 and the value of b is 4.

Here is another related example. In this example we pass a value to a method.

Suppose we have written a method

public static void incrementAndPrint(int x) {
  x++;
  System.out.println("x = " + x);
}

Here now is some code that uses this method:

int a = 3;
incrementAndPrint(a);
System.out.println("a = " + a);

When you pass a variable of primitive type as a parameter to a method, the value of that variable gets copied into the method parameter. Any thing that the method does to subsequently modify the parameter will be working with a copy of the original data. This does not affect the value of the variable you passed to the method.

In this example the code will first print that the value of x is 4 and then print that the value of a is 3.

The next examples involve the use of a simple class. Here is the code for that class.

public class Counter {
  private int value;

  public Counter(int startingValue) {
    value = startingValue;
  }

  public void increment() {
    value++;
  }

  public void print() {
    System.out.println("My value is " + value);
  }
}

Here now are some code snippets that make use of this class.

Counter a = new Counter(3);
Counter b = a;
b.increment();
b.print();
a.print();

Running this code results in the program printing the messages

My value is 4
My value is 4

The first message is what we expect, since we told b to increment before printing. The second message may be somewhat unexpected, because we never told a to increment its value.

What is going on here? To understand this example fully, it helps to understand in more detail what happens when you create and work with objects. Here is an illustration that shows in more detail what happens in the program when you execute the first statement above.

When you create an object with new, the computer creates the object in the program and then gives you some information about where the object is located. That location information is what gets stored in the object variable. This is illustated in the picture above, which shows the object itself as an entity that exists separate from the variable a. The variable a stores information about where the object is located, which is shown in the picture as an arrow running from the variable a to the object itself.

Object variables in Java are sometimes referred to as object reference variables, because their purpose is to refer or point you to the actual object.

The next picture illustrates what happens when we execute the statement that sets up the variable b:

Assigning a to b simply copies the location information from variable a to variable b. This results in a situation where we have two object variables that both refer to the same object. Since both variables refer to the same object, if we use either of the two variables to call a method that updates the state of the object, both variables will see the same change. Later, when we execute the print methods both methods will get called on the same object, which naturally will print the same value both times.

You can see the same phenomenon when you pass a variable of object type to a method. Suppose we have a method

public static void incrementAndPrint(Counter c) {
  c.increment();
  c.print();
}

and we execute this code:

Counter a = new Counter(3);
incrementAndPrint(a);
a.print();

This will result in the program printing

My value is 4
My value is 4

The picture below illustrates the situation just before we do the increment in the method:

Passing a as a parameter to the method results in the location information stored in a being copied into the parameter c. This results once again in a situation where two variables refer to the same object. If we then use c to increment the value in the object, a will see that change because a refers to the same object.

The null value

Now that we understand that object reference variables and the objects they refer to are separate things, we can set up situations where an object reference variable does not refer to any object.

The simplest way to do this is to set up an object variable and then forget to initialize it.

Counter a;
a.increment();
a.print();

NetBeans will immediately flag this as an error, because it will notice immediately that the object reference variable has not been initialized. This makes it impossible to compile and run this code.

A somewhat sneakier example is this one:

Counter a = null;
a.increment();
a.print();

NetBeans will not flag this as an error, because this time we are actually initializing the variable. The null value we are using to initialize the variable sets up the situation where a refers to nothing.

If we go ahead and run these statements the code will throw a null pointer exception when we try to execute the second line of code. This exception happens whenever you try to call a method using an object reference variable that does not actually point to an object.

Using object references to link objects together

In the next section of these notes I am going to put together an extended example of a class. This class will be useful to us in a larger example I will show on Friday. In addition, this example class will give me an opportunity to illustrate another application of object reference variables.

In computer science a stack is a special type of list that stores a collection of items arranged vertically. The stack supports two main operations, the push operation which puts a new item at the top of the list, and the pop operation that removes the item at the top of the list.

Here is picture that illustrates what a stack looks like after we push the values 2, 3, and 4 onto the top of an initially empty stack.

To implement this structure I am going to set up an arrangement in which each data item on the stack gets stored in a separate object. Those objects will get linked together using a system of object reference variables.

The first step is to define a simple class that can store the individual data items on the stack.

public class StackItem {
  public int value;
  public StackItem next;

  public StackItem(int x) {
    value = x;
    next = null;
  }
}

Using this helper class we can then set up our stack class:

public class Stack {
  private StackItem top;

  public Stack() {
    top = null;
  }

  public void push(int x) {
    StackItem newItem = new StackItem(x);
    newItem.next = top;
    top = newItem;
  }

  public boolean empty() {
    return top == null;
  }

  public int peek() {
    return top.value;
  }

  public void pop() {
    top = top.next;
  }
}

If we now run the following code

Stack S = new Stack();
S.push(2);
S.push(3);
S.push(4);

we will end up with a collection of objects that looks something like this:

The variable S refers to the Stack object. That object's top member variable refers to the StackNode at the top of the stack. The top two StackNode objects use their next members to point to the StackNode objects below them in the stack. The StackNode at the bottom of the stack retains the value of null to indicate that there is nothing below it in the stack.