AlgoMaster Logo

Reference Types

High Priority12 min readUpdated June 5, 2026
Listen to this chapter
Unlock Audio

Primitives store values directly, but most things in Java are objects, and objects live somewhere else. This lesson covers what a reference type is, how reference variables connect to objects in memory, what null actually means, and why == doesn't compare contents when used with objects. We'll keep the running shop example small so the memory picture stays clear.

What Counts as a Reference Type

Java has exactly 8 primitive types: int, long, short, byte, float, double, char, and boolean. Everything else is a reference type. That includes String, every array (String[], int[], anything with []), and every class you define yourself.

The key difference shows up in what the variable actually holds:

  • A primitive variable holds the value itself. int price = 10; and the slot for price literally contains 10.
  • A reference variable holds the address of an object that lives somewhere else in memory. String name = "Sara"; and the slot for name contains a reference (you can think of it as a pointer) to a String object stored on the heap.

A tiny program that uses both:

From the outside, both variables look similar. You assigned a value, you printed it. The difference is invisible until you pass variables around, compare them, or one of them is empty.

Stack vs Heap: Where Variables Live

Java splits memory into two regions that matter for this lesson: the stack and the heap. The stack holds local variables for the method that's currently running. The heap holds objects.

When you write int price = 10; inside main, Java carves out a slot on the stack for price and writes 10 into it. The value sits inside the slot, end of story.

When you write String customerName = "Sara";, two things happen:

  1. A String object holding the characters S, a, r, a is created on the heap.
  2. A slot for customerName is carved out on the stack, and the address of that heap object is written into the slot.

The variable doesn't contain "Sara". It contains a reference that points to where "Sara" lives.

The price slot is self-contained. The customerName slot is a pointer.

You don't manage this layout by hand. The JVM decides where things go and the garbage collector cleans up heap objects no one references anymore. What matters is knowing which kind of variable you're holding when you look at a line of code.

Creating Objects with new

Most reference values are created with the new keyword. When you write new SomeType(...), three things happen:

  1. The JVM allocates space for a new object on the heap.
  2. The constructor runs to initialize the object's fields.
  3. The expression evaluates to a reference pointing at the new object.

A small example using an array, which is a reference type with a slightly different new syntax:

new String[3] allocates a fresh array object on the heap big enough to hold 3 references. The variable cartItems then points to that array.

String is a special case because Java lets you skip new for string literals. Writing String customerEmail = "sara@example.com"; produces a String object too, but the literal goes through a separate mechanism called the String Pool. For now, treat string literals as if they create an object on the heap.

Every new allocates fresh memory. In a tight loop, building objects you don't actually need creates pressure on the garbage collector. Reuse objects or use primitives when you can.

null: The Absence of a Reference

A reference variable doesn't have to point at anything. null is the value that means "this reference points to no object." You assign it explicitly, or you read it from a field that hasn't been initialized.

Printing a null reference shows the text null. No crash. The trouble starts when you try to use the reference for anything other than passing it around or comparing it.

What's wrong with this code?

The variable holds no reference, so the JVM can't follow it to call .length(). You get a NullPointerException (often shortened to NPE):

Fix: Check for null before using the reference, or assign a real value:

A second place null appears without explicit assignment: reference-type fields default to null when a class is created, the same way primitive fields default to 0 or false. The defaults are summarized below.

Field typeDefault value
int, long, short, byte0
double, float0.0
booleanfalse
char''
Any reference type (String, arrays, classes)null

Local variables (the ones inside a method) don't get defaults. The compiler forces you to assign a value before reading them, which catches a lot of accidental nulls early.

Assigning One Reference to Another

Assigning one reference variable to another does not copy the object. Both variables end up pointing to the same object on the heap.

We only changed cartB[0], but cartA[0] changed too. That's because cartB = cartA copied the reference, not the array. After that line, both variables point at the same array object on the heap. Writing through one of them is visible through the other.

Compare with primitives, where assignment really does copy the value:

priceA keeps its original value because primitive assignment hands priceB its own slot containing a fresh 100. Changing one slot doesn't touch the other.

The reference-sharing behavior matters a lot once methods enter the picture: if you pass a reference into a method and the method mutates the object, the caller sees the change.

== vs .equals() for References

Comparison is the other place where the reference model leaks into your code. The == operator compares whatever sits inside the variable. For primitives that means values. For references it means addresses.

Two separate new String(...) calls produce two separate objects on the heap. The contents are the same, but the addresses are different, so == returns false. The equals method on String compares character by character, so it returns true.

A rule of thumb:

  • == answers "do these two variables point at the same object?"
  • .equals() answers "do these two objects have the same contents?" (when the class defines a meaningful equals method, which String does).

There's one catch with string literals when using == on strings:

This prints true because string literals share storage through the String Pool. Two literals with the same characters end up referencing the same pooled object, so == happens to work. That's a quirk of how the pool optimizes memory, not a general rule about strings. If even one side uses new String("..."), you're back to false. Use .equals() when you care about contents.

The takeaway here is just the mental model: == for "same object", .equals() for "same contents."

.equals() on String scans both strings, so it's O(n) in the length. == is a single pointer comparison. For very hot code paths comparing pooled or interned strings, == can be faster, but it's almost always wrong for general use.

Primitives vs References Side by Side

The whole lesson reduces to a few practical differences.

AspectPrimitiveReference
What the variable holdsThe actual valueAn address (a reference to an object on the heap)
Where the data livesStack slot for the variableObject lives on the heap; the variable on the stack holds the reference
Default field value0, 0.0, false, ''null
Can be nullNoYes
== behaviorCompares valuesCompares references (same object?)
Assignment (a = b)Copies the valueCopies the reference; both point to the same object

One assignment copied a value, the other copied a reference. Same syntax, very different effect.

Quiz

Reference Types Quiz

10 quizzes