Back to Java
2026-03-178 min read

Reference Overview (Java)

Learn Reference Overview (Java) step by step with clear examples and exercises.

Title: Java Reference Overview - A full guide for Mastering Java References

Why This Matters

Understanding Java references is crucial for both beginners and experienced programmers. Java references play a significant role in many real-world scenarios, such as multithreaded programming, garbage collection, and object manipulation. Moreover, understanding references can help you avoid common pitfalls and improve your problem-solving skills during interviews or exams.

Prerequisites

Before diving into Java references, it is essential to have a solid foundation in the following topics:

  1. Basic Java syntax (variables, operators, loops, control structures)
  2. Object-oriented programming concepts (classes, objects, inheritance, polymorphism)
  3. Understanding memory management in Java (garbage collection)
  4. Familiarity with fundamental data structures like arrays and linked lists
  5. A good understanding of the Java API, particularly the Object class and its methods
  6. Knowledge about primitive types and their wrapper classes (e.g., Integer, Double, etc.)

Core Concept

In Java, every object has a reference that points to its location in the heap memory. When you create an object using the new keyword, a new reference variable is created, which stores the address of the newly allocated object.

String str = new String("Hello, World!");

In this example, str is a reference variable that points to the memory location where the String object is stored. The String class is immutable, meaning once created, its contents cannot be changed. However, multiple references can point to the same object in memory.

Object References vs Primitive Types

Unlike primitive types (e.g., int, char, boolean), objects have references that hold their memory addresses. This difference is crucial when comparing values of primitive types and objects:

int a = 5;
int b = 5;
System.out.println(a == b); // true

String str1 = new String("Hello");
String str2 = new String("Hello");
System.out.println(str1 == str2); // false

In the above example, a and b are primitive variables holding integer values 5, which are compared directly. Since they have the same value, the comparison returns true. However, when comparing object references str1 and str2, they point to different memory locations, so the comparison returns false.

Null References

A null reference is a special value that indicates an object variable does not hold any memory address. It is important to check for null references before using them to avoid NullPointerExceptions:

String str; // initializes str as null
if (str != null) {
System.out.println(str);
} else {
System.out.println("str is null");
}

Reference Assignment

You can assign one reference variable to another, making both variables point to the same object:

String str1 = new String("Hello");
String str2 = str1;
System.out.println(str1 == str2); // true

Reference Equality

To check if two object references refer to the same object, you can use the == operator:

String str1 = new String("Hello");
String str2 = new String("Hello");
System.out.println(str1 == str2); // false

String str3 = str1;
System.out.println(str1 == str3); // true

Reference Comparison

To compare the values of objects, you should use the equals() method:

String str1 = new String("Hello");
String str2 = new String("Hello");
System.out.println(str1.equals(str2)); // true

Reference Comparison with Interned Strings

For interned strings (strings created with literal values), you can use both == and equals() to compare their values:

String str1 = "Hello";
String str2 = "Hello";
System.out.println(str1 == str2); // true
System.out.println(str1.equals(str2)); // true

Immutable Objects and Reference Equality

For immutable objects like String, the == operator can be used for comparison since their memory addresses are constant:

String str1 = "Hello";
String str2 = "Hello";
System.out.println(str1 == str2); // true

Reference Internning

In Java, the JVM internally manages a cache called String pool. If you create multiple strings with the same literal value, they will share the same memory location:

String str1 = "Hello";
String str2 = "Hello";
System.out.println(str1 == str2); // true

Autoboxing and Unboxing

Java provides automatic conversion between primitive types and their wrapper classes (e.g., Integer, Double, etc.). This process is called autoboxing when converting from a primitive to its wrapper class, and unboxing when converting back:

int a = 5;
Integer intWrapper = a; // Autoboxing
int b = intWrapper.intValue(); // Unboxing

Primitive vs Reference Types in Collections

When adding primitive types to collections like ArrayList, they are automatically wrapped in their respective wrapper classes:

ArrayList<Integer> list = new ArrayList<>();
list.add(5); // Autoboxing
int value = list.get(0).intValue(); // Unboxing

Worked Example

Create a simple Java program that demonstrates the concepts of object references, null references, and reference equality:

public class ReferenceExample {
public static void main(String[] args) {
String str1 = new String("Hello");
String str2 = new String("World!");

System.out.println("str1: " + str1);
System.out.println("str2: " + str2);

String str3 = str1; // Assigning reference
System.out.println("str3: " + str3);
System.out.println("str1 == str3: " + (str1 == str3));

String str4 = null; // Creating a null reference
System.out.println("str4 is null: " + (str4 == null));

String str5 = "Hello"; // Interned string
String str6 = "Hello";
System.out.println("str5 == str6: " + (str5 == str6));
}
}

Common Mistakes

  1. Comparing objects using == instead of equals() when the objects are not interned.
  2. Forgetting to check for null references before using them, leading to NullPointerExceptions.
  3. Assuming that primitive types and objects behave similarly in terms of equality checks (use == for primitives and equals() for objects).
  4. Failing to understand the difference between reference assignment and object creation.
  5. Not realizing the importance of autoboxing and unboxing when working with primitive types and their wrapper classes.
  6. Overlooking the impact of the String pool on reference equality checks.
  7. Misusing hashCode() method for comparing objects, which can lead to incorrect results.
  8. Failing to understand that the equals() method should implement the equals contract in custom classes.
  9. Using == instead of equals() when comparing interned strings without checking if they are interned first.
  10. Not understanding the difference between shallow and deep copying in Java.

Practice Questions

  1. Given the following code, what will be the output?
int a = 5;
Integer b = a;
System.out.println(b);
  1. What is the difference between == and equals() in Java?
  1. Explain how the String pool works in Java.
  1. Write a program that demonstrates the use of autoboxing and unboxing with multiple primitive types.
  1. How can you avoid NullPointerExceptions when working with null references in Java?
  1. What is the purpose of the equals() method in Java, and what should it implement for custom classes?
  1. Explain the difference between shallow copying and deep copying in Java. Provide an example of each.
  1. Given the following code, what will be the output?
String str1 = new String("Hello");
String str2 = "World!";
System.out.println(str1.equals(str2));
  1. What is the difference between a shallow copy and a deep copy in terms of object references?
  1. How can you implement a custom deep copy constructor in Java?

FAQ

  1. What is the difference between a reference variable and an object in Java?

A reference variable stores the memory address of an object, while an object is the actual instance of the class that occupies the allocated memory location.

  1. Why can't you use == to compare two objects in Java?

You cannot use == to compare two objects directly because it checks if both variables hold the same memory address, not their content. To compare object values, use the equals() method.

  1. What is the purpose of autoboxing and unboxing in Java?

Autoboxing and unboxing allow seamless conversion between primitive types and their wrapper classes without explicit casting. This feature simplifies working with collections and other scenarios where mixed primitive and object data are involved.

  1. What is the String pool in Java, and how does it affect reference equality?

The String pool is a cache managed by the JVM that stores interned strings (strings created with literal values) to save memory and improve performance. When you create multiple strings with the same literal value, they will share the same memory location, which can impact reference equality checks.

  1. What are some common mistakes when working with object references in Java?

Common mistakes include comparing objects using == instead of equals(), forgetting to check for null references before using them, assuming that primitive types and objects behave similarly in terms of equality checks, not understanding the difference between reference assignment and object creation, and failing to understand the importance of autoboxing and unboxing when working with primitive types and their wrapper classes.

  1. What is the purpose of the equals() method in Java, and what should it implement for custom classes?

The equals() method compares the values of two objects and returns true if they are equal. For custom classes, the equals() method should implement the equals contract, which means it should compare the state (attributes) of the object to determine equality.

  1. Explain the difference between shallow copying and deep copying in Java.

Shallow copying creates a new object with its own reference variables pointing to the same objects as the original object, while deep copying creates a new object with copies of all its attributes (nested objects, arrays, etc.). This is important when dealing with mutable objects that need to maintain their independence after being copied.

  1. Given the following code, what will be the output?
String str1 = new String("Hello");
String str2 = "World!";
System.out.println(str1.equals(str2));

The output will be false. This is because str1 and str2 are different objects, even though their values could be the same if they were interned strings. In this case, since they are not interned, they have different memory addresses, so the comparison returns false.

  1. What is the difference between a shallow copy and a deep copy in terms of object references?

A shallow copy creates new reference variables that point to the same objects as the original object, while a deep copy creates new copies of all the objects (including nested objects) referenced by the original object. This means that a shallow copy maintains the same object references, while a deep copy creates entirely new objects with their own memory addresses.

  1. How can you implement a custom deep copy constructor in Java?

To create a custom deep copy constructor in Java, you can use recursion to clone all nested objects and arrays. Here's an example for a simple Person class:

public class Person implements Cloneable {
private String name;
private int age;
private List<String> hobbies;

public Object clone() throws CloneNotSupportedException {
Person person = (Person) super.clone();
person.hobbies = new ArrayList<>(this.hobbies); // create a deep copy of the hobbies list
for (String hobby : person.hobbies) {
// if there are nested objects, clone them as well
if (hobby instanceof Person) {
Person nestedPerson = (Person) hobby.clone();
person.hobbies.set(hobbies.indexOf(hobby), nestedPerson);
}
}
return person;
}
// getters and setters omitted for brevity
}

In this example, the clone() method creates a

Reference Overview (Java) | Java | XQA Learn