Primitive and Object Reference Variables

 

Consider the following code:

  1. Class Foo
  2. {
  3. public static void main ( String [] args )
  4. {
  5. int i = 17;
  6. int j = 22;
  7. String name1 = new String( "Joe" );
  8. String name2 = new String( "Jane" );
  9. ...
  10. if ( i == j )
  11. System.out.println( "i equals j" );
  12. if ( name1 == name2 )
  13. System.out.println( "name1 equals name2" );
  14.  
  15. i = j;
  16. name1 = name2;
  17. if ( i == j )
  18. System.out.println( "i equals j" );
  19. if ( name1 == name2 )
  20. System.out.println( "name1 equals name2" );
  21. }
  22. }

Values for primitive variables such as i and j are stored directly in some RAM location.  However objects are never stored in variables.  Instead all objects are creating using a different area of RAM known as the heap.  Variables such as name1 and name2 do not contain objects, but rather the RAM address of objects.  For this reason Java refers to non-primitive variables not as object variables, but rather as object reference variables.

It may help to know something about how a compiler works.  When compiling a program a compiler builds a table known as the symbol table.  This table is added to for each variable seen in the program.  It has columns for the name, type, and RAM location for each variable.  Initially the RAM location is blank—the compiler fills in that column last.

Sample Symbol Table
Name Type RAM Address
args String array ref 100
i int 104
j int 108
name1 String ref 112
name2 String ref 116

Before producing compiled output (the .class file), the symbol table is filled in with actual RAM addresses to use for each variable.  Then when producing byte code the compiler replaces the name of variables with the RAM addresses from the symbol table.  The layout of RAM for this program would look something like the following, when the program gets to line 14:

[RAM layout of primitives and object references]

When producing byte code for lines 5 and 6 the Java compiler produces something like:

STORE "17" into RAM location 104
STORE "22" into RAM location 108

For lines 7 and 8 of the program above, the output is different.  It becomes something like:

CREATE String object in next available heap location with initial value "Joe"
    ⇒ Object created at RAM location 1460
STORE "1460" into RAM location 112
CREATE String object in next available heap location with initial value "Jane"
    ⇒ Object created at RAM location 1520
STORE "1520" into RAM location 116

So what is produced for lines 10 and 12?  The results are as you might expect.  For line 10:

COMPARE contents of RAM locations 104 and 108
    ⇒ set result to False (17 not equal to 22)

When the program is run you will not see “i equals j” displayed.  You won't see “name1 equals name2” either.  The reason is the byte code produced for line 12 is essentially the same as for line 10:

COMPARE contents of RAM locations 112 and 116
    ⇒ set result to FALSE (1460 not equal to 1520)

Many expect Java to compare the contents of RAM locations 1460 and 1520, but that isn't what Java does.  It is only comparing the object references, not the objects themselves.  To compare the objects and not the object references you can't use the "==" operator.  Instead you use the "equals" method, something like:

if ( name1.equals(name2) )

Will cause the Java compiler to produce byte code to compare the contents of the String objects in RAM locations 1460 and 1520.

After lines 15–15, the memory looks like this:

[RAM layout of primitives and object references]

It should be clear that this time, you will see “i equals j” (lines 17–18).  You will also see “name1 equals name2” (lines 19–20).  Note that no variable refers to the Joe string anymore.  It should be clear that at this point (after line 16) not only does “name1 == name2”, but also that “name1.equals(name2)”.