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); } }