Object References

IB Syllabus: B3.1: Understand how objects are stored and referenced in memory.


Key Concepts

Primitives vs Objects in Memory

Java stores primitive types (int, double, boolean, char) directly in the variable: the variable holds the actual value.

Java stores objects differently. The variable does not hold the object itself: it holds a reference (a memory address that points to where the object lives in memory).

Primitive:   int age = 25;
             age → [ 25 ]          ← the value IS in the variable

Object:      Student s = new Student("Alice");
             s → [ address ] → [ name: "Alice", grade: 0 ]
                  ↑ reference       ↑ the actual object (in heap memory)

This distinction matters because it changes how assignment, comparison, and method parameters behave.

Aliasing: Two References, One Object

When you assign one object variable to another, you copy the reference, not the object. Both variables now point to the same object in memory. This is called aliasing.

BankAccount a1 = new BankAccount("Alice", 500.0);
BankAccount a2 = a1;  // a2 copies the reference, NOT the object

a2.deposit(200.0);

System.out.println(a1.getBalance());  // 700.0
System.out.println(a2.getBalance());  // 700.0
a1 = BankAccount("Alice", 500.0)
a2 = a1    # a2 copies the reference, NOT the object - exactly like Java

a2.deposit(200.0)

print(a1.get_balance())   # 700.0
print(a2.get_balance())   # 700.0

Both a1 and a2 point to the same object. Depositing through a2 changes the object that a1 also references.

Before:   a1 ──┐
               ├──► [ Alice, 500.0 ]    ← ONE object, two references
          a2 ──┘

After:    a1 ──┐
               ├──► [ Alice, 700.0 ]    ← still ONE object, both see the change
          a2 ──┘

This is one of the most common sources of bugs. Students expect a2 = a1 to create an independent copy. It does not: it creates a second name for the same object. To create an actual copy, you must construct a new object and copy the values.

Contrast with Primitives

Primitives behave differently because the variable holds the value directly:

int x = 10;
int y = x;   // copies the VALUE, not a reference

y = 20;

System.out.println(x);  // 10 -- unchanged
System.out.println(y);  // 20
# Python has no primitives - but numbers are IMMUTABLE (they cannot be
# changed in place), so the behaviour matches Java's value copy
x = 10
y = x

y = 20    # rebinds y to a new value; x is untouched

print(x)   # 10 - unchanged
print(y)   # 20

Changing y does not affect x because each variable holds its own independent copy of the value.

null, No Object

A reference variable that does not point to any object holds the special value null. It means “this variable exists but references nothing.”

Student s = null;  // s exists but points to no object
s = None   # None is Python's null - s exists but points to no object

Calling a method on a null reference causes a NullPointerException: one of the most common runtime errors in Java:

Student s = null;
System.out.println(s.getName());  // NullPointerException!
s = None
print(s.get_name())   # AttributeError! (Python's version of the same crash)

The fix: always check for null before calling methods, especially when working with arrays of objects where not every slot may be filled:

if (s != null) {
    System.out.println(s.getName());
}
if s != None:
    print(s.get_name())

When working with object arrays, always check for null before calling methods on array elements. new Student[30] creates 30 null references, not 30 Student objects. This null check is worth a mark in almost every IB exam method question.

== vs .equals(): Comparing Objects

For primitives, == compares values:

int a = 5;
int b = 5;
System.out.println(a == b);  // true -- same value
a = 5
b = 5
print(a == b)   # True - same value

For objects, == compares references (memory addresses), not the data inside:

Student s1 = new Student("Alice", 11);
Student s2 = new Student("Alice", 11);

System.out.println(s1 == s2);  // false -- different objects in memory
s1 = Student("Alice", 11)
s2 = Student("Alice", 11)

print(s1 == s2)   # False - different objects in memory
# Python's == calls a special method (__eq__) which, unless a class defines
# its own, compares identity - so on our classes it behaves like Java's ==

Even though s1 and s2 have identical data, they are two separate objects at different memory addresses. == checks whether both variables point to the same object, not whether the objects contain the same values.

To compare the content of objects, use .equals():

String a = new String("hello");
String b = new String("hello");

System.out.println(a == b);      // false -- different objects
System.out.println(a.equals(b)); // true -- same content
a = "hello"
b = "hello"

print(a == b)   # True - for strings, Python's == compares CONTENT,
                # doing the job of Java's .equals()
# Python has no .equals() - == is the correct string comparison in Python

For Strings, always use .equals() for comparison, never ==. The == operator checks if two String variables point to the exact same object in memory, which is usually not what you want. "hello".equals(other) checks if the characters match. (Python is the opposite: == compares string content and is exactly what you want.)

Passing Objects to Methods

When you pass a primitive to a method, Java copies the value. The method works with its own copy: changes do not affect the original.

When you pass an object to a method, Java copies the reference. The method receives a reference to the same object: changes to the object inside the method ARE visible to the caller.

public static void doubleBalance(BankAccount acc) {
    double current = acc.getBalance();
    acc.deposit(current);  // modifies the ORIGINAL object
}

BankAccount myAcc = new BankAccount("Alice", 100.0);
doubleBalance(myAcc);
System.out.println(myAcc.getBalance());  // 200.0 -- changed!
def double_balance(acc):      # a free-standing function receiving the object
    current = acc.get_balance()
    acc.deposit(current)      # modifies the ORIGINAL object

my_acc = BankAccount("Alice", 100.0)
double_balance(my_acc)
print(my_acc.get_balance())   # 200.0 - changed!
# Python passes references the same way: changing the object inside
# the function is visible to the caller

The method received a copy of the reference (pointing to the same object), so acc.deposit() modifies the same object that myAcc references.


Worked Examples

Example 1: Tracing Aliased References

BankAccount a = new BankAccount("Alice", 1000.0);
BankAccount b = new BankAccount("Bob", 500.0);
BankAccount c = a;  // c is an alias for a

c.deposit(300.0);
b.deposit(100.0);
a = BankAccount("Alice", 1000.0)
b = BankAccount("Bob", 500.0)
c = a    # c is an alias for a

c.deposit(300.0)
b.deposit(100.0)
After line a.balance b.balance c.balance Notes
Lines 1-2 1000.0 500.0 Two separate objects
Line 3 1000.0 500.0 1000.0 c points to the SAME object as a
Line 4 1300.0 500.0 1300.0 c.deposit changes a’s object too
Line 5 1300.0 600.0 1300.0 b is independent

Example 2: Null Check Pattern

Student[] roster = new Student[5];
roster[0] = new Student("Alice", 95);
roster[1] = new Student("Bob", 82);
// roster[2], [3], [4] are still null

int count = 0;
for (int i = 0; i < roster.length; i++) {
    if (roster[i] != null && roster[i].getGrade() > 90) {
        count++;
    }
}
System.out.println(count);  // 1 (only Alice)
# Python lists normally start empty, but a list of Nones mirrors
# Java's array of null references for this example
roster = [None] * 5           # five None ("null") entries
roster[0] = Student("Alice", 95)
roster[1] = Student("Bob", 82)
# roster[2], [3], [4] are still None

count = 0
for i in range(len(roster)):
    if roster[i] != None and roster[i].get_grade() > 90:
        count = count + 1
print(count)   # 1 (only Alice)

The roster[i] != null check MUST come first. Java uses short-circuit evaluation, if the left side of && is false, the right side is never evaluated. Without the null check, roster[2].getGrade() would throw a NullPointerException. (Python’s and short-circuits the same way, guarding against the AttributeError.)


Quick Check

Q1. What does BankAccount b = a; do?

Q2. What happens when you call s.getName() and s is null?

Q3. Two Student objects have identical name and grade. What does s1 == s2 return?

Q4. If you pass a BankAccount object to a method and the method calls deposit(), is the original object changed?


Trace Exercise

Trace the references and object state through this code.

Trace: Aliasing and Independence

BankAccount x = new BankAccount("Alice", 100.0);
BankAccount y = new BankAccount("Bob", 200.0);
BankAccount z = x;
z.deposit(50.0);
y.deposit(25.0);
After linex.balancey.balancez points to
Lines 1-2 100.0 200.0 --
Line 3: z = x
Line 4: z.deposit(50)
Line 5: y.deposit(25)

Spot the Error

This code prints "different" even though both strings contain "hello". Click the buggy line, then pick the fix.

1String a = new String("hello"); 2String b = new String("hello"); 3 4if (a == b) { 5 System.out.println("same"); 6} else { 7 System.out.println("different"); 8}

Pick the fix:


Predict the Output

What does this print?

BankAccount a = new BankAccount("Alice", 500.0);
BankAccount b = a;
b.deposit(100.0);
System.out.println(a.getBalance());

What does this print?

int x = 10;
int y = x;
y = 99;
System.out.println(x);

Practice Exercises

Core

  1. Predict the output: Trace this code and predict what each println outputs. Explain why.
    Student s1 = new Student("Alice", 90);
    Student s2 = s1;
    Student s3 = new Student("Alice", 90);
    
    System.out.println(s1 == s2);
    System.out.println(s1 == s3);
    
  2. Null check practice: Write a method countPassing(Student[] students) that returns how many students have a grade of 50 or above. The array may have null entries. Include the null check.

  3. Explain the difference: In your own words, explain why == returns true for int a = 5; int b = 5; but false for two Student objects with identical data.

Extension

  1. Reference tracing: Draw a memory diagram showing what happens at each step:
    Student a = new Student("Alice", 90);
    Student b = new Student("Bob", 80);
    Student c = a;
    a = b;
    b = c;
    

    After all five lines, what do a, b, and c reference? Has any data been lost?

  2. Method parameter behaviour: Explain why this method changes the original account’s balance but does NOT change which object myAcc points to:
    public static void reset(BankAccount acc) {
        acc.withdraw(acc.getBalance());  // empties the account
        acc = new BankAccount("New", 0); // does NOT affect the caller
    }
    

Challenge

  1. Deep copy: The Student class has name (String) and grade (int). Write a method copyStudent(Student original) that creates and returns a completely independent copy (a new object with the same values). Prove your copy is independent by modifying the copy and showing the original is unchanged.

Connections

  • Prerequisites: Classes and Objects: creating objects with new
  • Prerequisites: The this Keyword: this is a reference to the current object
  • Related: Strings: String comparison with .equals() vs ==
  • Next: UML Class Diagrams. Visual representation of class structure and relationships
  • Forward: Aggregation. Objects as attributes create reference chains

© EduCS.me — A resource hub for Computer Science education

This site uses Just the Docs, a documentation theme for Jekyll.