Chapter 4. Reference Types

Reference types hold references to objects and provide a means to access those objects stored somewhere in memory. The memory locations are irrelevant to programmers. All reference types are a subclass of type java.lang.Object.

Table 4-1 lists the five Java reference types.

Table 4-1. Reference types

Reference types

Brief description


Provides a way to associate metadata (data about data) with program elements.


Provides a fixed-size data structure that stores data elements of the same type.


Designed to provide inheritance, polymorphism, and encapsulation. Usually models something in the real world and consists of a set of values that holds data and a set of methods that operates on the data.


A reference for a set of objects that represents a related set of choices.


Provides a public API and is “implemented” by Java classes.

Comparing Reference Types to Primitive Types

There are two categories of types in Java:reference types and primitive types. Table 4-2 shows some of the key comparisons between them. See Chapter 3 for more details.

Table 4-2. Reference types compared to primitive types

Reference types

Primitive types

Unlimited number of reference types, as they are user-defined.

Consists of boolean and numeric types: char, byte, short, int, long, float, and double.

Memory location stores a reference to the data.

Memory location stores actual data held by the primitive type.

When a reference type is assigned to another reference type, both will point to the same object.

When a value of a primitive is assigned to another variable of the same type, a copy is made.

When an object is passed into a method, the called method can change the contents of the object passed to it but not the address of the object.

When a primitive is passed into a method, only a copy of the primitive is passed. The called method does not have access to the original primitive value and therefore cannot change it. The called method can change the copied value.

Default Values

Instance and Local Variable Objects

Instance variable objects (objects declared at the class level) have a default value of null. null references nothing.

Local variable objects (objects declared within a method) do not have a default value, not even a value of null. Always initialize local objects because they are not given a default value. Checking an uninitialized local variable object for a value (including a value of null) will result in a compile-time error.

Although object references with a value of null do not refer to any object on the heap, objects set to null can be referenced in code without receiving compile-time or runtime errors.

	Date dateOfParty = null;
	// This is ok
	if (dateOfParty == null) {

Invoking a method on a reference variable that is null or using the dot operator on the object will result in a java.lang.NullPointerException.

	String theme = null;
	// This will result in an exception
	// if theme is still set to null
	if (theme.getLength( ) > MAX_LENGTH) {


Arrays are always given a default value whether they are declared as instance variables or local variables. Arrays that are declared but not initialized are given a default value of null.

In the code below, the gameList array is initialized but not the individual values, meaning that the object references will have a value of null. Objects have to be added to the array.

	// This declared array named gameList
	// is initialized to null by default
	   Game[] gameList;

	// This array has been initialized but
	// the object references are still null
	// since the array contains no objects
	   gameList = new Game[10];

	// Add a Game object to the list
	// Now the list has one object
	   gameList[0] = new Game( );

Conversion of Reference Types

An object can be converted to the type of its superclass (widening) or any of its subclasses (narrowing).

The compiler checks conversions at compile time and the JVM checks conversions at runtime.

Widening Conversions

  • Widening implicitly converts a subclass to a parent class (superclass).

  • Widening conversions do not throw runtime exceptions.

  • No explicit cast is necessary.

    	String s = new String( );
    	Object o = s; // widening

Narrowing Conversions

  • Narrowing converts a more general type into a more specific type.

  • Narrowing is a conversion of a superclass to a subclass.

  • An explicit cast is required. To cast an object to another object, place the type of object you are casting to in parentheses immediately before the object you are casting.

    	Object a = new Object;
    	String b = (String)a; // Cast to String
  • Illegitimate narrowing results in a ClassCastException.

  • Narrowing may result in a loss of data/precision.

Objects cannot be converted to an unrelated type—that is, a type other than one of its subclasses or superclasses. Doing so will generate an inconvertible types error at compile time. The following is an example of a conversion that will result in a compile-time error due to inconvertible types:

	Object c  = "balloons";
	int d = (int) c;  // compile-time error

Converting Between Primitives and Reference Types

The automatic conversion of primitive types to reference types and vice versa is called autoboxing and unboxing, respectively. For more information, see Chapter 3.

Passing Reference Types into Methods

When an object is passed into a method as a variable:

  • A copy of the reference variable is passed, not the actual object.

  • The caller and the called methods have identical copies of the reference.

  • The caller will also see any changes the called method makes to the object. Passing a copy of the object to the called method will prevent it from making changes to the original object.

  • The called method cannot change the address of the object, but it can change the contents of the object.

The following example illustrates passing reference types and primitive types into methods and the effects on those types when changed by the called method:

	void roomSetup( ) {

	  // Reference passing
	  Table table = new Table( );
	    // Length will be changed

	    // Primitive passing
	    // Value of chairs not changed
	    int chairs = 8;

	  void modTableLength(Table t) {

	  void modChairCount(int 1) {
	     1 = 10;

Comparing Reference Types

Using the Equality Operators ==

The != and == equality operators:!= and

  • Are used to compare the memory locations of two objects. If the memory addresses of the objects being compared are the same, the objects are considered equal.

  • Are not used to compare the contents of the two objects.

In the following example, guest1 and guest2 have the same memory address, so the statement "They are equal" will be output.

	Guest guest1 = new Guest("name");
	Guest guest2 = guest1;
	if (guest1 == guest2)
	  System.out.println("They are equal")

In the following example, the memory addresses are not equal, so the statement "They are not equal" will be output.

	Guest guest3 = new Guest("name");
	Guest guest4 = new Guest("name");
	if (guest3 == guest4)
	  System.out.println("They are equal.")
	  System.out.println("They are not equal")

Using the equals( ) method

To compare the contents of two class objects, the equals( )method from class Object can be used or overridden.


By default, the equals( ) method uses only the == operator for comparisons. This method has to be overridden to really be useful.

For example, if you want to compare values contained in two instances of the same class, you should use a programmerdefined equals( ) method.

Comparing strings

There are two ways to check whether strings are equal in Java, but the definition of “equal” for each of them is different. Typically, if the goal is to compare character sequences contained in two strings, the equals( ) method should be used.

  • The equals( ) method compares two strings, character by character, to determine equality. This is not the default implementation of the equals( ) method provided by the class Object. This is the overridden implementation provided by class String.

  • The == operator checks to see whether two object references refer to the same instance of an object.

Below is a program that shows how strings are evaluated using the equals( ) method and the == operator. For more information on how strings are evaluated, see the “String Literals” section in Chapter 2.

	class MyComparisons {

	  // Add string to pool
	  String first = "chairs";
	  // Use string from pool
	  String second = "chairs";
	  // Create a new string
	  String third = new String ("chairs");

	 void myMethod( ) {

	  // Contrary to popular belief, this evaluates
	  // to true. Try it!
	  if (first == second) {
	    System.out.println("first == second");

	  // This evaluates to true
	  if (first.equals(second)) {
	    System.out.println("first equals second");
	  // This evaluates to false
	  if (first == third) {
	    System.out.println("first == third");
	  // This evaluates to true
	  if (first.equals(third)) {
	    System.out.println("first equals third");
	 } // End myMethod( )
	} //end class


Objects of the String class are immutable. Objects of the StringBuffer and StringBuilder classes are mutable.


enum values can be compared using == or the equals( )method, as they return the same result. The == operator is used more frequently to compare enumeration types.

Copying Reference Types

When reference types are copied, either a copy of the reference to an object is made, or an actual copy of the object is made, creating a new object. The latter is referred to as cloning in Java.

Copying a Reference to an Object

When copying a reference to an object, the result is one object with two references. In the example below, closingSong is assigned a reference to the object pointed to by lastSong. Any changes made to lastSong will be reflected in closingSong and vice versa.

	Song lastSong = new Song( );
	Song closingSong = lastSong;

Cloning Objects

Cloning results in another copy of the object, not just a copy of a reference to an object. Cloning is not available to classes by default. Note that cloning is usually very complex, so you should consider a copy constructor instead.

  • For a class to be cloneable, it must implement the interface Cloneable.

  • The protected method clone( ) allows for objects to clone themselves.

  • For an object to clone an object other than itself, the clone( ) method must be overridden and made public bythe object being cloned.

  • When cloning, a cast must be used because clone( )returns type object.

  • Cloning can throw a CloneNotSupportedException.

Shallow and deep cloning

Shallow and deep cloning are the two types of cloning in Java.

In shallow cloning:

  • Primitive values and the references in the object being cloned are copied.

  • Copies of the objects referred to by those references are not made.

In the example below, leadingSong will be assigned the values in length and year, as they are primitive types, and references to title and artist, as they are reference types.

	Class Song {
	  String title;
	  Artist artist;
	  float length;
	  int year;
	  void setData( ) {...}
	Song firstSong = new Song( );
	try  {
	  // Make an actual copy by cloning
	  Song leadingSong = (Song)firstSong.clone( );
	} catch (CloneNotSupportedException cnse)
	    cnse.printStackTrace( );
	}  // end

In deep cloning:

  • The cloned object makes a copy of each of its object’s fields, recursing through all other objects referenced by it.

  • A deep-clone method must be programmer-defined, as the Java API does not provide one.

  • Alternatives to deep cloning are serialization and copy constructors. (Copy constructors are often preferred over serialization.)

Memory Allocation and Garbage Collection of Reference Types

When a new object is created, memory is allocated. When there are no references to an object, the memory that object used can be reclaimed during the garbage collection process. For more information on this topic, see Chapter 15.

Get Java Pocket Guide now with O’Reilly online learning.

O’Reilly members experience live online training, plus books, videos, and digital content from 200+ publishers.