Reference Types

Everything in Java that isn't a primitive is a reference type — a variable that holds not the object itself, but a handle to it on the heap.

Java has two kinds of types: primitive types (byte, int, double, boolean, and friends) and reference types. A reference type variable does not store an object directly — it stores a reference (essentially an opaque handle) that points to an object living on the heap. Classes, interfaces, arrays, enums, and records are all reference types, and understanding how references behave is central to understanding how Java programs actually work.

What Counts as a Reference Type

Any type that is not one of the eight primitives is a reference type. That includes every class you write or use (String, ArrayList, your own classes), every interface, every array type (int[], String[][]), every enum, and every record. A variable of a reference type never holds an object's fields inline the way a primitive variable holds a value inline — it holds a reference to an object elsewhere.

String name = "Ada";      // reference to a String object
int[] scores = {90, 85};  // reference to an array object
Student s = new Student(); // reference to a Student object

References vs. the Objects They Point To

It helps to keep two things mentally separate: the reference (a small value, conceptually similar in role to an address) and the object (the actual data, living on the heap). The reference variable's job is only to say 'here is where the object lives' — it is not the object.

A reference variable pointing at heap data name="Ada", age=20

The exact bit pattern of a reference and where objects physically live are JVM implementation details, not something the Java language specifies or that your code can rely on.

null: A Reference to Nothing

Any reference type variable can hold the special literal null, meaning it currently points to no object. Primitives can never be null — this is one of the fundamental differences between the two type categories.

Calling a method or accessing a field through a null reference throws a NullPointerException at runtime — the JVM has nothing to dereference.

Student s = null;   // legal: s refers to no object
int x = null;       // COMPILE ERROR: int is primitive, cannot be null

Object Identity vs. Object Content

Because a reference variable holds a pointer-like handle rather than the object's data, two separate objects with identical field values are still two distinct objects with two distinct identities. Java tracks this distinction directly through the == operator on reference types.

== on reference types compares whether two references point to the exact same object (identity), never whether the objects' contents match. .equals() is a method whose behavior is entirely up to the class — Object's default .equals() also just compares identity, but classes commonly override it for content-based comparison.

Student a = new Student("Ada");
Student b = new Student("Ada");
System.out.println(a == b);          // false: two different objects
System.out.println(a.equals(b));     // depends on Student's equals() override

Assignment Copies the Reference, Not the Object

When you assign one reference variable to another, Java copies the reference value (the handle) — it does not clone the object. After the assignment, both variables point to the same underlying object; this is called aliasing.

Only one Student object exists here. a and b are two separate variables holding the same reference value, so a mutation through b is visible through a.

Student a = new Student("Ada");
Student b = a;          // b now points to the SAME object as a
b.setName("Grace");
System.out.println(a.getName()); // prints "Grace" — a and b are aliases

Aliasing in Practice

Aliasing is not a bug — it's how Java's model works, and it's exploited deliberately (e.g. two data structures sharing the same underlying nodes). But it is a frequent source of surprise for newcomers, especially when passing objects into methods or storing them in collections, because every place that holds 'the same object' sees every mutation to it.

List<Student> roster = new ArrayList<>();
Student s = new Student("Ada");
roster.add(s);
roster.add(s);          // same object added twice — not two students
s.setName("Grace");
System.out.println(roster.get(0).getName()); // "Grace"
System.out.println(roster.get(1).getName()); // "Grace" too

Arrays Are Reference Types Too

Arrays, even arrays of primitives like int[], are objects in Java and are accessed through references. This is why an array parameter passed to a method can have its elements mutated by that method — the method receives a copy of the reference, but that copy still points to the one shared array object.

int[] data = {1, 2, 3};
int[] alias = data;
alias[0] = 99;
System.out.println(data[0]); // 99 — same array object

Reference Equality vs. Value Equality

Distinguishing 'same reference' from 'same content' matters constantly in Java. Use == only when you specifically want identity comparison (rare — mostly for enum constants, null checks, or intentional singleton checks). Use .equals() when you want to know whether two objects represent the same value, and make sure the class you're comparing has actually overridden equals() meaningfully — otherwise .equals() silently falls back to identity comparison inherited from Object.

String x = new String("hi");
String y = new String("hi");
System.out.println(x == y);        // false — different String objects
System.out.println(x.equals(y));   // true — String overrides equals() by content

Classes, Interfaces, Enums, Records as Reference Types

All of Java's higher-level type constructs share the same reference semantics described above, even though they differ in how they're declared and used:

An interface variable holds a reference to some object of a class implementing that interface. An enum constant is, under the hood, a full-fledged object of the enum type. A record is a special compact class — its instances are still ordinary objects accessed via references, just with immutable components and generated equals/hashCode/toString by default.

class Car { }              // reference type
interface Drivable { }     // reference type (used as a variable's declared type)
enum Color { RED, GREEN }  // reference type — each constant is an object
record Point(int x, int y) { } // reference type — an immutable data carrier

Practical Usage: Passing References Into Methods

Because Java is strictly pass-by-value, and a reference type variable's 'value' is the reference itself, passing a reference-type argument copies the reference — the method gets its own variable holding the same handle. The method can mutate the object through that handle, but reassigning the parameter inside the method does not affect the caller's variable.

static void rename(Student p) {
    p.setName("Changed");  // mutates the shared object — visible to caller
    p = new Student("New"); // reassigns the LOCAL copy only — caller unaffected
}

public static void main(String[] args) {
    Student s = new Student("Original");
    rename(s);
    System.out.println(s.getName()); // "Changed", not "New"
}

Edge Cases

A few subtleties worth internalizing:

1) Comparing a reference to null with == is always safe and never throws — it just checks whether the reference is empty.

2) String literals are pooled/interned by the JVM, so two identical literal Strings often (but this is an implementation detail, not a guarantee you should code against for correctness) share one object — use .equals() for String comparison regardless.

3) A reference to an object does not make that object immutable; final only restricts reassignment of the reference variable, covered separately under constants.

Mental Model

Think of a reference type variable as a sticky note with an address written on it, and the object as the house at that address. Copying the sticky note (assignment) doesn't build a new house — it just gives you a second note pointing at the same house. Comparing sticky notes with == asks 'do these notes have the same address written on them?' Comparing with .equals() asks a question the house itself defines: 'do these two houses count as the same, by whatever rule the house's blueprint specifies?'

Quick Reference

Reference types: classes, interfaces, arrays, enums, records
Default value (field): null
Can be null: yes (primitives cannot)
== on references: identity comparison (same object?)
.equals(): content comparison, IF the class overrides it
Assignment: copies the reference, not the object (aliasing)
Method args: reference value is copied; object itself is shared

Key Takeaways

A reference type variable stores a handle to an object on the heap, not the object's data inline.

null means 'no object' and is only possible for reference types.

== compares reference identity; .equals() compares content, but only if the class overrides it meaningfully.

Assignment and method arguments copy the reference value, producing aliases that share the same underlying object.

Arrays, enums, and records are all reference types and follow the same aliasing rules as ordinary classes.

if (a == b) { /* comparing Student objects for equal content */ } if (a.equals(b)) { /* comparing Student objects for equal content */ }

== on reference types checks identity, not content. Use .equals() (with an overridden implementation) to compare object content.

Student b = a; b.setName("X"); // "only changing b's copy" Student b = new Student(a); // an explicit copy constructor, if independent objects are wanted

Assignment does not clone objects — b and a become aliases of one object, so mutating through b also affects what a sees.

Integer x = null; // or int x = 0;

Primitives can never hold null. Only reference types (including wrapper classes like Integer) can.

What is the difference between a reference type variable and the object it refers to?

The variable holds a reference (a handle/pointer-like value) to the object; the object itself, with its fields, lives separately on the heap. The variable is not the object.

Why can a reference type variable be null but a primitive variable cannot?

null represents 'points to no object,' which only makes sense for something that holds a reference. A primitive variable holds an actual value directly, so there is no 'reference' to be empty.

Two Student objects have identical field values. What does a == b evaluate to, and why?

false, because == on reference types compares whether both variables hold the reference to the same object in memory, not whether their contents match.

If StudentB = studentA; and then studentB's field is changed, does studentA see the change? Why?

Yes. Assignment copies the reference, not the object, so studentA and studentB become aliases pointing at the same single object.

Are arrays reference types in Java, even arrays of primitives like int[]?

Yes. Any array, regardless of its element type, is itself an object accessed through a reference.

Why does passing an object to a method allow the method to mutate the object's fields, but not make the caller's variable point to a different object?

Java copies the reference value into the method's parameter (pass-by-value). The method's copy points to the same object, so field mutations are visible outside — but reassigning the parameter only changes the local copy, leaving the caller's original reference untouched.

Is a record a reference type or a primitive-like type?

A reference type. Records are a compact form of class declaration; instances are ordinary heap objects accessed by reference, just with generated immutability-supporting behavior.

Write a small program with a class Box holding an int value, that demonstrates aliasing: create one Box, assign it to a second variable, mutate through the second variable, and print the value through the first.

class Box { int value; Box(int value) { this.value = value; } } public class Demo { public static void main(String[] args) { Box a = new Box(10); Box b = a; b.value = 99; System.out.println(a.value); // prints 99 } }

The program prints 99, because a and b are aliases referring to the same Box object — mutating through b is visible through a.

What does this program print, and why?

It prints 5. reset() reassigns its local parameter p to a brand-new Point object, but that only changes the method's own copy of the reference — the caller's p1 still points to the original Point(5).

Write code that correctly checks whether two String references, built with new String("abc"), represent the same text, and explain why == would be wrong here.

String s1 = new String("abc"); String s2 = new String("abc"); boolean sameText = s1.equals(s2); // true s1 == s2 would be false because new String(...) deliberately creates a distinct object each time, so == (identity comparison) fails even though the text is identical..equals() compares the actual character content, which String overrides to do correctly, yielding true.

class Point { int x; Point(int x) { this.x = x; } }

public class Trace {
    static void reset(Point p) {
        p = new Point(0);
    }
    public static void main(String[] args) {
        Point p1 = new Point(5);
        reset(p1);
        System.out.println(p1.x);
    }
}