References with a view

Welcome to the first JavaWorld Questions & Answers column

Welcome to the first installment of the JavaWorld Questions & Answers column. We will endeavor to answer your questions about Java, so be sure to send them to javaqa@javaworld.com.

With this being the inaugural installment, no one has sent in any questions yet, so we'll begin by discussing an aspect of the Java language that confuses many a programmer: references.

Background

Java is like C and C++, but better tasting. Its extensive -- and intentional -- similarity to C and C++ is a good thing for many programmers, as it eases the transition to Java. Unfortunately, the many similarities often cause developers to miss the crucial distinctions that make Java unique. The terminology used by each of these languages is often the same but with sometimes subtle differences in meaning.

C has its base types and pointer types. C++ modified those a bit and added its flavor of reference types. Java tightly specifies its primitive types, gets rid of any sort of pointer type, and has much cleaner reference types.

All of this jumbling of terminology and semantics evokes programmer consternation, so let's build a clear picture of what is really going on.

Base/integral/primitive/built-in data types

We won't go much into these types, as they are pretty much the same in C, C++, and Java. Java makes them a bit simpler for us developers by rigidly specifying the sizes. Java also added a true boolean type, and requires that type in conditional control constructs (such as, the if statement test).

The main point to be made is that these primitive types adhere to value semantics. We don't really care much about the receptacle holding the value. We care only about the actual value itself (the number 5, or the count of iterations through a loop, etc.).

Pointers

C and C++ allow the construction and use of pointers that can be directed pretty much everywhere. They may have some type associated with what they are directed at, or they may be typeless. This tremendous flexibility certainly has its uses but it also provides an awful lot of rope with which to hang yourself (something we have all done at one time or another).

The rather basic semantic model of pointers matches itself nicely onto the underlying hardware models making for some speedy code. Unfortunately, it's such a low-level model that it really hinders programming safety and robustness, and makes it very difficult to do things like automated garbage collection, memory movement/defragmentation, or distributed computing in a clean and efficient manner.

C++ references

References were added to C++ to try to make a lot of things that were done in C with pointers a bit cleaner and safer. This hasn't really made things that much better because they never got rid of the pointer types. This means that with the additions of references, C++ became more complicated without becoming much more powerful.

References, quite constrained in C++, must be initialized when they are declared and cannot be changed afterwards. They may allow for some increases in execution-time efficiency over a pointer-based solution, but this says more about the pain of pointers than the virtue of C++ references.

Java references

References in Java are neither pointers nor references like those in C++. This fact creates misunderstanding among beginning Java programmers, as evidenced by multitudinous newsgroup postings.

Java references (referred to as "references" from here on out unless specifically noted) are magical handles to instances of Java class types. A reference directs our attention to an actual object of a specified type. It doesn't matter how it does that and we should not need to know or care about its microscopic implementation.

Think of a reference as one of those electronic room keys at a fancy resort hotel. Let's first create a Room class so we can create the rooms in our hotel:

public class Room
{
    private int roomNumber;
    private int numBeds;
    public Room ()              { room (0); }
    public Room (int roomNum)           { room (roomNum); }
    public Room (int roomNum, int beds)     { room (roomNum); beds (beds); }
    public synchronized int room ()     { return roomNumber; }
    public synchronized void room (int roomNum) { roomNumber = roomNum; }
    public synchronized int beds ()     { return numBeds; }
    public synchronized void beds (int beds)    { numBeds = beds; }
}

The code presented here is just an edited version of the core code. Fully annotated versions of each class are available by following the links embedded in each class name.

Now, let's start building our hotel by creating some rooms and some keys:

public class Hotel1
{
    public static void main (String args[])
    {
    Room roomKey1;          // Step 1.
    Room roomKey2;
    roomKey1 = new Room (666);  // Steps 2, 3, 4, & 5.
    roomKey2 = new Room (1138, 4);
//  ^^^^^^^^   ^^^^^^^^ ^^^^^^^^^
//     A         B & D  C
    }
}

For our purposes, there are five steps involved in creating a room and a master room key to gain access to the room. Note that while in real life we have locksmiths to help us gain access to rooms for which we've lost the keys, in Java, once all of the keys are lost, the room is recycled.

The first step creates the room key itself; that is, it defines the reference variable (which defaults to null).

The rest of the steps are all mashed into a single Java statement. Part B in the sample code calls up the construction foreperson and tells her to have an actual room created. Part C calls the appropriate interior decorator who fixes up the room as per the specified criteria. The special electronic code that represents the room is made available in Part D by the construction foreperson and is then programmed into the specific room key in part A.

In computerese: The new operator allocates space for an instance of the specified class object and initializes the memory to appropriate default values. It then invokes the appropriate constructor method for that class, which is given the specified arguments. The new operator then returns the reference itself, which is immediately assigned to the reference variable.

We can have multiple keys for the same room:

    ...
    Room roomKey3, roomKey4;
    roomKey3 = roomKey1;
    roomKey4 = roomKey2;

We have thus made copies of the given keys. The rooms themselves are untouched in this process. So now we have two keys to room 666 and two keys to room 1138.

A given room key may be programmed to work only for one particular room at any given time, but it can be changed to work for some other room (after you find out they have stuck you in a smoking room on the ground floor rather than a non-smoking room with a view and your cranky spouse complains vociferously). Let's change the duplicate key for the sad smoking room with a bad view (which is room 666) to the non-smoking room with a view, 1138:

    ...
    roomKey3 = roomKey2;

So we have one key for room 666 and three keys for room 1138. We shall keep one key for each room at the front desk as master keys, just in case someone loses his key. We can give the visitors roomKey3 and roomKey4 for their stay.

Anyone with a key to a specific room can muck about with the room and the other folks with keys will not know about it until they access the room again. Let's change the room out from under our guests and see how upset they get. We shall remove one of the beds that's already in the room using our master key:

    ...
    roomKey2.beds (3);

Now, when the guests look at their room they will see the change:

    ...
    roomKey4.printData ();

References and arrays

Like C and C++, Java can have arrays of primitive types or of class types. Arrays in C/C++ are basically just syntactic sugar for pointers. In Java, however, arrays are first-class citizens. In this discussion we shall jump right into using arrays of references. We will cover arrays further in a future installment.

Let us expand on our rather paltry excuse for a hotel by creating an entire wing of the hotel. We do this by building a whole key ring of master keys and then build each of the rooms.

public class Hotel2
{
    // Number of rooms per wing.
    public static final int roomsPerWing = 13;
    public static void main (String args[])
    {
    Room[] masterKeyRing;               // Step 1.
    masterKeyRing = new Room [roomsPerWing];    // Steps 2-5.
/*  ^^^^^^^^^^^^^   ^^^^^^^^^^^^^^^^^^^^^^^
 *      A          B, C, D, & E
 */
    int floorNum = 1;
    for (int i = 0; i < roomsPerWing; i++)       // Steps 6-9.
        {
        masterKeyRing [i] = new Room (floorNum * 1000 + i,
                      (0 == (i % 2)) ? 2 : 1);
        }
    for (int i = 0; i < roomsPerWing; i++)       // Steps 10-11.
        masterKeyRing[i].printData();
    }
}

Each step in this process is directly analogous to the steps in the earlier example. Step 1 specifies that the master key ring is a contiguous group of room keys.

Steps 2 through 5 are the main new thing in this example. Not to worry though, since it is merely a slightly new twist on what we did earlier. Rather than creating an actual room, the construction foreperson is told to build a contiguous group of room keys. The number of room keys is specified in the brackets. All room keys are created blank. The construction foreperson hands over the entire group of keys, which are placed on the master key ring.

Steps 6 through 9 are nearly identical with Steps 2 through 5 of the previous example except that instead of having a bunch of loose keys floating around, we are putting all of the keys onto the key ring. Note that the floor numbers are given in the thousands so that when we complete our huge resort, all of the room numbers will have the same format. Note also that we all odd-numbered rooms have a single (large) bed while the even-numbered rooms have two beds.

Steps 10 and 11 access the particular key at the given location of the key ring and then use the key to get the room to print out some information (this is going to be high-tech resort so rooms should be smart).

References and linked data structures

A lot of people who are just learning Java get confused into thinking that because it does not have pointers there is no way to do complex, linked data structures such as linked lists, binary trees, and graphs. By this point you should be able to see the fallacy in that assumption.

We shall illustrate this by recasting our previous example using a simple, doubly linked list rather than the built-in arrays.

The simple linked-list package is composed of two classes. Each element of the list is a LinkedListNode:

public class LinkedListNode
{
    private LinkedListNode nextNode;
    private LinkedListNode prevNode;
    private Object dataItem;
    // ...
}

Each LinkedListNode contains a reference to the node preceding it in the list and a reference to the node following it. It also contains a generic reference to any class instance that will be used to refer to the actual user-provided list data.

The linked list contains a single head-and-tail node and a count of the nodes in the list.

public class LinkedList
{
    private LinkedListNode headAndTail;
    private int numNodes;
    // ...
}

The special head-and-tail node is nice because it actually simplifies the code. The node count also allows us to optimize some common cases.

The neat thing is that our revised Hotel code is nearly identical to our array-based version:

public class Hotel3
{
    public static final int roomsPerWing = 13;
    public static void main (String args[])
    {
    LinkedList masterKeyRing;       // Step 1.
    masterKeyRing = new LinkedList();   // Steps 2-5.
    int floorNum = 1;
    for (int i = 0; i < roomsPerWing; i++)   // Steps 6-9.
        {
        masterKeyRing.insertAt (i,
                    new Room (floorNum * 1000 + i,
                          (0 == (i % 2)) ? 2 : 1));
        }
    for (int i = 0; i < roomsPerWing; i++)   // Steps 10-12.
        ((Room) masterKeyRing.getAt(i)).printData();
    }
}

Step 1 states that the master key ring is a list. Note that it is represented by a generic list; that it is a list of room keys is merely a result of a convention we agreed upon. We could gain back the compile-time type safety by wrapping the generic LinkedList inside of a RoomLinkedList class (but that's left as an exercise for the reader).

Steps 2 through 5 hearken back to our first example. We build and initialize a new LinkedList, which is subsequently used as the master key ring.

Steps 6 through 9 are functionally identical to the corresponding steps in the previous example but the syntax has changed a bit. In Java, arrays and the [] subscripting operator are built into the language. Since Java does not support user-definable operator overloading, we must use the regular functional form.

The LinkedList provides the insertAt() method, which takes the index location in the list where the new node should be inserted as its first argument. The second argument is the instance object to be stored by the list. Note that no cast is needed to pass a descendent class to something that expects one of its parents.

Related:
1 2 Page 1
Page 1 of 2