Object References

Goals

Concepts

Language

Library

Lesson

The variables you've worked with so far have used primitive types. These variables set aside certain bits to represent the actual value assigned to the variable. Consider a Java int variable named width and assigned the value 3. Java will reserve four bytes and store the value 0b00000000000000000000000000000011. This value will be stored in memory set aside for the current scope.

Storing a primitive value in scope memory.
final int width = 3;
Scope Memory
Variable Name Value
width 0b00000000000000000000000000000011 (3)

Object References

There are other values in Java that are not primitive types; these are called objects. An object takes up many more bytes than a primitive value. Exactly how many bytes an object takes up isn't important at this stage. What is important is that objects are not stored with the variable in the scope memory, but are stored in a separate area called the heap.

Perhaps the most well-known object type in Java is the String, which represents a sequence of characters. (Note that String is capitalized.) You've used the string type from the very beginning when you typed System.out.println("Hello, World!") in your first Java program. You can assign a String value to a variable, but because a String is not a primitive type, the String contents will be stored separately in the heap.

If the contents of the String object are stored in the heap, what is stored locally in the variable? If you think back to the lesson on indirection, you may remember the store owner who made several lists, putting a code in a product list to indicate the supplier in another list. A variable for an object also uses indirection; instead of holding the value itself as it would with a primitive type, a variable for an object type holds a reference to the object's location in heap memory.

Storing an object value in heap memory.
final int width = 3;
final String greeting = "Hello, World!"
Scope Memory
Variable Name Value
width 0b00000000000000000000000000000011 (3)
greeting 1234567891111111…
Heap Memory
Memory Location Value
1234567891111111… "Hello, World!"

Object Types

In upcoming lessons you'll learn how to create your own object types, but here are two object types that come with Java.

Strings

Java provides an object that represents a sequence or string of characters. Normally to create an object in Java you would use the keyword new, as you will see below, but with strings you simply enter the value in quotes. You can also create new strings by joining together or concatenating other strings using the plus + sign. But strings are still objects, and string variables contain references.

Creating and concatenating String objects.
final String hello = "Hello";
final String world = "Wo" + "rld";
final String greeting = hello + ", " + world + "!";  //resulting string is "Hello, World!"
Scope Memory
Variable Name Value
hello 1234567891111111…
world 1234567892222222…
greeting 1234567893333333…
Heap Memory
Memory Location Value
1234567891111111… "Hello"
1234567892222222… "World"
1234567893333333… "Hello, World!"

Arrays

An array is another type of object. As with most objects except for String, you must use the keyword new to create an array. However like String Java provides special characters to make arrays easier to create. The resulting object is a sequence of values of a single type, sort of like a row of cubbyholes to place shoes in. Each value in an array is called an element. For primitive types the values are set to some default value, such as 0 for the int type.

Creating arrays and setting element values.
final int[] numbers = new int[3];  //three elements; values default to 0
numbers[0] = 123;
numbers[2] = 789;
Scope Memory
Variable Name Value
numbers 1234567891111111…
Heap Memory
Memory Location Value
1234567891111111…
0 1 2
123 0 789

Because an array is not a primitive object, it can provide more information than just the values it contains. An array can indicate its length by using .length with the name of the variable referencing the array, such as numbers.length in the example above. Even though the .length identifier is added to the variable name, Java will process the reference indirection automatically and provide the length of the actual array object, as shown in the following example.

Examples of working with arrays.
final int[] numbers = new int[5];  //declare array of 5 ints, initially set at 0
final int howManyNumbers = numbers.length;
System.out.println(howManyNumbers); //prints 5
numbers[0] = 2;  //set first int to the value 2
number[4] = 99;  //set last int to the value 99
number[5] = 3;  //ERROR; the array is zero-based
final double[] moreNumbers = new double[]{1.23, 4.56, 7.89};  //declaration and initialization
final String[] names = new String[] {"Peter", "Paul", "Mary"};
System.out.println(names[0] + ", " + names[1] + " and " + names[2]); //prints "Peter, Paul and Mary"

Multiple References

Because object variables use a layer of indirection via an object reference, more than one variable can reference the same object in heap memory! If two variables reference the same object, after you modify the object via one variable, you will see the changed values via the other variable.

Creating arrays and setting element values.
final int[] numbers1 = new int[3];
numbers1[0] = 12;
numbers1[2] = 56;
final int[] numbers2 = numbers1; //numbers1 and numbers2 reference the same object
numbers2[0] = 34;
final int[] numbers3 = new int[3];
numbers3[0] = 78;
System.out.println(numbers1[0]); //prints 34
System.out.println(numbers2[0]); //prints 34
System.out.println(numbers3[0]); //prints 78
Scope Memory
Variable Name Value
numbers1 1234567891111111…
numbers2 1234567891111111…
numbers3 1234567892222222…
Heap Memory
Memory Location Value
1234567891111111…
0 1 2
34 0 56
1234567892222222…
0 1 2
78 0 0

Reference Scope

Because objects are stored in the heap, multiple variables can reference the same object—even if those variables are in different scopes! But what happens when one of the variables goes out of scope? Consider the following variation of the above example:

Referencing an object from different scopes.
int[] numbers1;  //define a variable but do not initialize it
{ //create a new scope
  final int[] numbers2 = new int[3]; //create a new array
  numbers2[0] = 34;
  numbers1 = numbers2; //make numbers1 reference the same array as numbers2
  System.out.println(numbers1[0]); //prints 34
  System.out.println(numbers2[0]); //prints 34
}
//numbers2 is now out of scope and no longer exists!
System.out.println(numbers1[0]); //prints 34
Scope Memory
Variable Name Value
numbers1 1234567891111111…
numbers2 1234567891111111…
Heap Memory
Memory Location Value
1234567891111111…
0 1 2
34 0 56

Because number2 was defined in a separate scope, as soon as the scope ends the variable numbers2 and its value disappear, exactly as you learned in previous lessons. But why then is numbers1 able to access the array even after number2 has gone out of scope and disappeared? Remember that numbers2 does not hold the array object that was created; rather numbers2 holds a reference to a new array, and when numbers2 goes out of scope only its reference pointer disappears—the object still lives on in the heap. That is how numbers1, which still referenced the new array, was able to access the array values.

Reference Equality

Perhaps one of the most tricky aspects of reference variables is comparing their values. If the equality operator == is used with two object variables, it compares the references themselves, not the contents of the objects! The result will evaluate to true only if both variables reference the same object. Let's revise the examples above and compare some of the variables:

Creating arrays and setting element values.
final int[] numbers1 = new int[] {12, 34, 56};
System.out.println(numbers1[0]); //prints 12
System.out.println(numbers1[1]); //prints 34
System.out.println(numbers1[2]); //prints 56
final int[] numbers2 = numbers1; //numbers1 and numbers2 reference the same object
System.out.println(numbers2[0]); //prints 12
System.out.println(numbers2[1]); //prints 34
System.out.println(numbers2[2]); //prints 56
final int[] numbers3 = new int[] {12, 34, 56};
System.out.println(numbers3[0]); //prints 12
System.out.println(numbers3[1]); //prints 34
System.out.println(numbers3[2]); //prints 56
System.out.println(numbers1 == numbers2); //prints true
System.out.println(numbers2 == numbers3); //prints false
Scope Memory
Variable Name Value
numbers1 1234567891111111…
numbers2 1234567891111111…
numbers3 1234567892222222…
Heap Memory
Memory Location Value
1234567891111111…
0 1 2
12 34 56
1234567892222222…
0 1 2
12 34 56

There are three variables: numbers1, numbers2, and numbers3; but there are only two array objects, as shown in the heap memory table. Comparing numbers1 == numbers2 evaluates to true because the two variables contain the same reference; they both reference the same array object in heap memory. But comparing numbers1 == numbers3 evaluates to false, because numbers1 and numbers2 contain different reference values; they reference different objects in heap memory.

Final References

This course has stressed the importance of using final to prevent accidentally changing the value stored in a variable. Using final with object references may cause some confusion initially, as the contents of a referenced object may be modified even if the variable referencing is marked final! This makes perfect sense if you remember that the value contained by such variables are references to objects, not the objects themselves. An object reference marked as final may not be assigned a reference to a different object, but the object it references may still be modified.

Modifying array contents of final variables.
final int[] numbers1 = new int[3];
final int[] numbers2 = numbers1; //numbers1 and numbers2 reference the same object
numbers2[0] = 789; //the object can be modified even though the variable is final
System.out.println(numbers1[0]); //prints 789
System.out.println(numbers2[0]); //prints 789
numbers2 = new int[3]; //cannot reassign a final variable
System.out.println(numbers1 == numbers2); //prints true
Scope Memory
Variable Name Value
numbers1 1234567891111111…
numbers2 1234567891111111…
Heap Memory
Memory Location Value
1234567891111111…
0 1 2
789 0 0

null References

An object reference therefore is a variable containing an internal value, which is never shown, which “points” to the object in heap memory. Java has a special reference value null which indicates that variable should point to no object at all! The null value works with any type. You can initialize a reference with null or set the reference to null later.

Because setting a reference to null means that the variable doesn't point to any object, trying to access a null variable as if it referenced an object will result in a NullPointerException error.

Setting array references to null.
final int[] numbers1 = new int[] {12, 34, 56};
System.out.println(numbers1[0]); //prints 12
System.out.println(numbers1[1]); //prints 34
System.out.println(numbers1[2]); //prints 56
int[] numbers2 = numbers1; //numbers1 and numbers2 reference the same object
System.out.println(numbers2[0]); //prints 12
numbers2 = null;
System.out.println(numbers2[2]); //results in a NullPointerException
Scope Memory
Variable Name Value
numbers1 1234567891111111…
numbers2 null
Heap Memory
Memory Location Value
1234567891111111…
0 1 2
12 34 56

You learned earlier in this lesson that when a variable goes out of scope, the variable disappears—but if the variable was referencing an object in heap memory, the object will continue to exist as long as there are other references to that same object. Even if a reference variable has not gone out of scope, setting its value to null also severs its link to the object, allowing the object to be garbage collected. If we additionally set numbers1 to null in the example above, the array will no longer have references to it and the JVM will be free to reuse its memory at a later time

Setting all array references to null to allow garbage collection
int[] numbers1 = new int[] {12, 34, 56};
System.out.println(numbers1[0]); //prints 12
System.out.println(numbers1[1]); //prints 34
System.out.println(numbers1[2]); //prints 56
int[] numbers2 = numbers1; //numbers1 and numbers2 reference the same object
System.out.println(numbers2[0]); //prints 12
numbers2 = null;
numbers1 = null;
Scope Memory
Variable Name Value
numbers1 null
numbers2 null
Heap Memory
Memory Location Value
1234567891111111…
0 1 2
12 34 56

Review

Boat piers.
Boat piers: Sailboat and canoe as objects.

The illustration shows three object reference variables, represented by piers on a lake, and only two objects: a sailboat and canoe. Both Jane and John used to jointly own the same sailboat but now only Jane owns it. John's pier is falling apart and no longer holds the sailboat.

One object reference has gone out of scope, but the object it referenced, the sailboat, still exists in heap memory because there is another object reference to the same sailboat object. The variable referencing the canoe object has been set to null, so at some point the canoe object will go over the waterfall and be garbage collected.

Sailboat and canoe object reference review.
final char[] janeBoat = new char[] {'s', 'a', 'i', 'l'};
final char[] billBoat = new char[] {'c', 'a', 'n', 'o', 'e'};
{  //new scope
  final char[] johnBoat = janeBoat;
}
//johnBoat out of scope
billBoat = null;
Scope Memory
Variable Name Value
janeBoat 1234567891111111…
johnBoat 1234567891111111…
billBoat null
Heap Memory
Memory Location Value
1234567891111111…
0 1 2 3
's' 'a' 'i' 'l'
1234567892222222…
0 1 2 3 4
'c' 'a' 'n' 'o' 'e'

Gotchas

In the Real World

Think About It

Self Evaluation

Task

Create a program that solves the following problem, printing out the answers.

Create a compressed archive of your entire project directory and send it to your teacher.

See Also

Acknowledgments