Tourist Guide to Java Pointer Traps


Joseph Bergin
Pace University
jbergin@pace.edu


Java doesn't have pointer variables, but it has reference variables which are equivalent in concept, but use a simpler syntax. All variables that refer to objects (values of a class type) are reference variables. They refer to the object indirectly and are implemented with something like a machine address (though it could be more sophisticated). From an implementation viewpoint, objects are just "cells" on the "free store" and are always allocated with the new operator. They are deallocated automatically by the garbage collector when no active variable in the program references them any longer.

Pointer and reference variables give the programmer great flexibility, but they can lead to difficult to find and correct errors in your programs. In this section we will examine the common problems and give hints about how to avoid them. This should be useful to novice Java programmers.

While it is often useful to ignore the distinction, here it will be critical to realize that a variable is NOT the object it references. Variables are declared to have a certain type, and they may reference that type or any subtype. Objects are created with new ... and never change their type once created. Many variables can refer to the same object in a valid Java program.

In the following we will suppose the following types and variables

abstract class Animal
{...
}

class Lion extends Animal
{...
}

class Tiger extends Animal
{	
	public Tiger() {...}
	public void growl(){...}
}

Tiger first = null;
Tiger second = new Tiger();
Tiger third;

Dereference null

It is an error to dereference null. This is the safest error to make (in Java), since the system will catch the error at run time. In other languages, things may not be so nice.


first.growl();	// ERROR, first is null.

A null pointer does not point to anything. Dereferencing obtains the thing the pointer points to. This error is will result in a NullPointerException being thrown. If the exception is not caught (in a try-catch construct) the program will be halted with an error message pointing to the line with the error.

Note that

third.growl(); // ERROR, third has not been initialized.

is a different kind of error. We haven't initialized third with any value, not even null. The Java compiler will insist that we initialize each variable before we use it. This error would be caught by the compiler. Recent version of Java may do the initialization to null for us in most cases.

Aliasing Problems

Having more than one name for the same cell can also add flexibility to our programs, but there are some dangers. For example, after executing


third = new Tiger();
first = third;

we will eventually want to send messages to the object referred to by first. As long as we are aware that variables third and first reference the same object, all will be well. However, if we send third a message which results in the object changing its internal state, then we need to be aware that these state changes are visible via all variables that reference that object.

This is actually a benefit of object-oriented programming and is a fundamental technique. For example, if you have a vector that contains Tigers and you need to modify a tiger, you don't need to remove it from the vector, then modify it, and then replace it. Any reference to it can be used to modify it and all references will be able to then take advantage of the changes.

Losing Cells

It may be an error to give a new value to a pointer that references an object unless you either first (a) create an alias of the cell, or (b) are willing to abandon the cell.

This is an error that is not caught by the system and may not even be an error, depending on the logic of the program. It simply results in free store cells that are reclaimed by the garbage collector. The program may make no further use of these cells.

second = third; // Possible ERROR. The old value of second is lost.    

You can make this safe by first assuring that there is no further need of the old value of second or assigning another pointer the value of second.


first = second;
second = third;	//OK

Note that giving second a value in other ways (NULL, new...) is just as much a potential error and may result in losing the object that it points to.

The Java system will throw an exception (OutOfMemoryError) when you call new and the allocator cannot allocate the requested cell. This is very rare and usually results from runaway recursion.

Note that, from a language point of view, abandoning objects to the garbage collector are not errors at all. It is just something that the programmer needs to be aware of. The same variable can point to different objects at different times and old values will be reclaimed when no pointer references them. But if the logic of the program requires maintaining at least one reference to the object, they become errors.

Novices often make the following error.

Tiger tony = new Tiger();
tony = third; // Error, the new object allocated above is reclaimed. 

What you probably meant to say was:

Tiger tony = null;
tony = third; // OK.

Improper Casting

It is illegal to cast a reference variable to a class when the object to which the variable then points is not a member of that class or a subclass. It is illegal to cast a reference to an interface type when the object to which the variable then points does not implement that interface.

It is possible to cast a variable of one reference type to another reference type if the cast is valid. The cast is checked.

Lion leo = new Lion();
Tiger tony = (Tiger)leo; // Always illegal and caught by compiler.  

The above is never legal since a Tiger can never refer to a Lion object and a cast doesn't change the type of an object, just affirms that it has a type.

Animal whatever = new Lion(); // Legal.
Tiger tony = (Tiger)whatever; // Illegal, just as in previous example.
Lion leo = (Lion)whatever; // Legal, object whatever really is a Lion. 

The first statement above is legal since a variable of a superclass type can refer to an object of that type or any subclass type. The second is illegal, but won't be caught until runtime (by most compilers) since the compiler may not do enough analysis of the program to determine that whatever isn't a Tiger. The third is legal, but a run time check will be used to verify the fact. The variable whatever really is a Lion.

An improper cast caught at runtime will result in a ClassCastException being thrown.


Last Updated: September 11, 2007