Free Java Tutorials >> Table of contents >> References

1. Object References

Object types are references

All object types (classes, enums, etc) are references. A reference is a pointer to a location in computer memory. Let's compare the behavior of primitive (non object) types with object types:

//In a method somewhere:
int x = 5;
int y = x;

x = 42;
System.out.println(y); //prints 5

Notice that with primitive types such as int, modifying x to equal 42 did not change y in any way. This is because x and y actually hold the integer value within them. Let's compare this behavior to that of objects:

class Thing {
	int data;
}
void someMethod() {
	Thing x = new Thing();
	x.data = 5;
	Thing y = x; 
	
	x.data = 42;
	System.out.println(y.data); //prints 42
}

Here, the program above only has one Thing object, even though there are two object references: x and y. The line Thing x = new Thing() is the only time the Thing constructor is run, and the memory location of that constructed object is stored in x. Then, y is assigned to the same memory address. Thus, modifications of the field data at location x also affect the field data at location y.

void methods and objects

As a consequence of references, void functions can modify objects referenced by parameters:

class Thing {
	int data;
}
public class MyProgram {
	public static void doubleData(Thing x) {
		x.data *= 2;
	}
    public static void main(String[] args) {
        Thing a = new Thing();
		a.data = 21;
		System.out.println(a.data); //21
		doubleData(a);
		System.out.println(a.data); //42
    }
}

The above doubleData method does not return a value. Instead, it changes the data field referenced by argument x.

== vs .equals

Because object variables are references, there can be two separate objects with difference references but with the same field values. Consider the variables x and y in this example:

class Thing {
	int data;
}
public class MyProgram {
    public static void main(String[] args) {
        Thing x = new Thing();
		Thing y = new Thing();
		x.data = 42;
		y.data = 42;
		System.out.println(x.data); //42
		System.out.println(y.data); //42
		System.out.println(x); 
		System.out.println(y);  //different than println(x)
		
		System.out.println(x.data == y.data);
		System.out.println(x == y);
    }
}

We see that x does not equal y, even though their data fields are equivalent. This is because they are different memory addresses. Typically, we solve this problem by creating a boolean equals method:

class Thing {
	int data;
	boolean equals(Thing other) {
		return this.data == other.data;
	}
}
public class MyProgram {
    public static void main(String[] args) {
        Thing x = new Thing();
		Thing y = new Thing();
		x.data = 42;
		y.data = 42;
		System.out.println(x.data); //42
		System.out.println(y.data); //42
		System.out.println(x); 
		System.out.println(y);  //different than println(x)
		
		System.out.println(x.data == y.data);
		System.out.println(x == y);
		System.out.println(x.equals(y)); //true
    }
}

We see that x.equals(y) is true. Similarly, you should not use == to compare Strings in Java. Instead, you should use the String.equals method. Equals methods also exist in many other Java classes, but you should read their documentation before use.

The null object value and NullPointerException

Because object variables are references, object variables can also refer to nothing, which is called "null". Here's an example that crashes:

class Thing {
	int data;
}
public class MyProgram {
    public static void main(String[] args) {
        Thing x = null;
		System.out.println(x.data);
    }
}

The error thrown is a "NullPointerException". One way you can avoid them is by checking if object references are null before using them:

class Thing {
	int data;
}
public class MyProgram {
    public static void main(String[] args) {
        Thing x = null;
		System.out.println("x start as : "+x);
		if( x == null ) {
			x = new Thing();
		}
		System.out.println("now we can get x.data: "+x.data);
    }
}

2. Array References

Arrays are references

Like objects and classes, Arrays are actually references. Here is an illustrative example:

//In a method somewhere:
int[] x = {10,20,30,40};
int[] y = x;

System.out.println(x);  //memory address
System.out.println(y);  //same address

x[0] = 42;
System.out.println(y[0]); //now also 42

In the above example, changing the elements of x also change the elements of y. Similarly, we can write void functions that modify arrays:

import java.util.*;
public class MyProgram {
	public static void cumulativeSum(int[] x) {
		for(int i = 1 ; i < x.length; i++) {
			x[i] += x[i-1];
		}
	}
    public static void main(String[] args) {
        int[] x = {10,20,30,40};
        System.out.println("Array is: "+Arrays.toString(x));
		
		cumulativeSum(x); //no need to get return value
		
		System.out.println("Array is: "+Arrays.toString(x));
    }
}

Two dimensional arrays

Regular one dimensional arrays are references, and thus two dimensional arrays are references of references (they are arrays of arrays). Usually, we call the outer array the "rows", and the inner arrays the "columns". Rows can have unequal number of columns, and indeed a row can even reference a null subarray.

Here we get a single row's reference to a subarray of columns ("b"):

public class MyProgram {
    public static void main(String[] args) {
		//Create a rectangular two dimensional array
		int[][] a = new int[5][3];
		int[] b = a[0]; //get first row of a
		b[0] = 42;
		
		System.out.println(a[0][0]);  //prints 42		
    }
}

We can even initialize the rows without initializing columns:

import java.util.*;
public class MyProgram {
    public static void main(String[] args) {
		//Create a rectangular two dimensional array
		int[][] a = new int[4][]; //4 rows each with null array for columns
		System.out.println("Rows prior to subarray initialization:");
		for(int[] row : a) System.out.println(Arrays.toString(row));
		
		int[] firstrow = new int[3]; //3 zeros
		firstrow[0] = 42;
		a[0] = firstrow;
		
		a[1] = new int[]{10,20};
		
		a[3] = new int[0]; //empty array (different from null reference)
		
		System.out.println("Rows after subarray initialization:");
		for(int[] row : a) System.out.println(Arrays.toString(row));
    }
}

3. Wrapper Classes, Autoboxing

Autoboxing

Autoboxing is the automatic conversion by the Java compiler from certain primitive types and their object counterparts. The following types support autoboxing:

Primitive type Wrapper class
boolean Boolean
byte Byte
char Character
float Float
int Integer
long Long
short Short
double Double

Thus in Java, the following two lines are equivalent:

Integer x = 5;
Integer x = new Integer(5);

The first line from above compiles into the second.

Autoboxed operators and methods

Operators and methods that take an autoboxed object type will automatically accept the primitive type and vice versa. Thus, the following code for "a" is equivalent to the code for "b":

public class MyProgram {
    public static void main(String[] args) {
		Integer a = 5;
		System.out.println(a + 2);
		
		Integer b = new Integer(5);
		int value = a.intValue();
		System.out.println(value + 2);
    }
}

As you can see, Java transparently calls "intValue" on the "Integer" class when it is used as part of arithmetic expressions.

Null Pointers and autoboxed types

Since classes can be null, Integer and other class autoboxed types can be null. This allows for arrays of Integer to contain null values. When you attempt to use these values as if they are a primitive, Java transparently calls methods such as "intValue". If the object is null, this will result in a NullPointerException:

import java.util.*;
public class MyProgram {
    public static void main(String[] args) {
		Integer[] x = {10,20,30};
		System.out.println("Array is: "+Arrays.toString(x));
		
		//Setting x[0] to null
		x[0] = null;
		System.out.println("Array is now: "+Arrays.toString(x));
		
		//uncomment this line for an exception:
		//System.out.println(x[0] + 1);
    }
}

For each loops an autoboxing

For each loops can transparently convert between types, but similarly cannot handle null types without causing an exception. This program will crash:

import java.util.*;
public class MyProgram {
    public static void main(String[] args) {
		Integer[] x = {null,20,30};
		for(int value : x) {
			System.out.println(value); //crashes when converting null to int
		}
    }
}

Previous: Enums

Next: Generics