Skip to content
Part IA Michaelmas Term

Primitive versus Reference Types

The Two Kinds of Type

Every type in Java falls into one of two categories: primitive or reference. Understanding the distinction is essential for reasoning about assignment, parameter passing, equality comparison, and null handling.

Primitive Types

Primitives represent simple, atomic values. They are:

  • Fixed-size: each primitive type occupies a known number of bytes, specified by the language specification and independent of platform
  • Stored directly by value: a variable of primitive type holds the actual value, not a pointer to it
  • Never null: a primitive variable always contains a value (even if uninitialised, it gets a default zero value as a field)
  • Live on the stack or inline in objects: primitives are not heap-allocated unless they are fields of a heap-allocated object

The Eight Primitive Types

TypeSizeRangeDefaultWrapper Class
byte8 bits128-128 to 1271270Byte
short16 bits32768-32\,768 to 3276732\,7670Short
int32 bits231-2^{31} to 23112^{31} - 1 (~ ±2.1\pm 2.1 billion)0Integer
long64 bits263-2^{63} to 26312^{63} - 10LLong
float32 bitsIEEE 754 single-precision (~7 significant digits)0.0fFloat
double64 bitsIEEE 754 double-precision (~15 significant digits)0.0dDouble
booleannot precisely defined (JVM-dependent)true or falsefalseBoolean
char16 bitsUnicode characters U+0000 to U+FFFF\u0000Character

The sizes in the table are fixed by the Java Language Specification. An int is always 32 bits, regardless of whether the JVM runs on a 32-bit or 64-bit platform. This is a key difference from C, where int may be 16, 32, or 64 bits depending on the compiler and target.

Literals

  • Integer literals (42) are int by default; append L for long (42L)
  • Floating-point literals (3.14) are double by default; append f for float (3.14f)
  • char literals are enclosed in single quotes ('A', '\n', '\u0041')
  • boolean literals are true and false

Reference Types

Reference types include classes, arrays, interfaces, enums, and String. A variable of reference type holds a reference — a handle or pointer to an object allocated on the heap. The variable itself does not contain the object’s data; it contains the memory address (abstractly) of where the object lives.

String s = new String("hello");

The variable s is a reference. The actual string object lives on the heap. The reference is what gets stored in the variable.

Key Properties of Reference Types

  • Assignment copies the reference, not the object:
Point p1 = new Point(3, 4);
Point p2 = p1;

p2 now refers to exactly the same object as p1. Modifying p2’s state will be visible through p1. This is called aliasing.

  • Can be null: a reference variable can hold null, meaning “no object.” Attempting to call a method on null throws a NullPointerException at runtime.

  • Equality has two meanings:

    • == compares references — are these the same object in memory?
    • .equals() compares values — do these objects represent the same logical value? (Behaviour depends on whether the class overrides equals.)
  • Live on the heap: the object itself is always heap-allocated. The reference variable may live on the stack (if it is a local variable) or be a field of another heap object.

Memory Layout

Stack and heap memory layout

  • Stack: holds local variables (primitives and references) and method call frames. Fast allocation/deallocation (just move the stack pointer). Each thread has its own stack.
  • Heap: holds all objects and arrays. Managed by the garbage collector. Slower allocation, but objects can outlive the method that created them.

A local variable of type int stores the integer value directly on the stack. A local variable of type Point stores a reference on the stack; the Point object’s fields (x and y, which are primitives) live inside the object on the heap.

Why the Distinction Matters

Parameter Passing

Java is pass-by-value, always. For primitives, the value is the number itself — the method receives a copy, and mutations to the parameter do not affect the caller’s variable. For references, the value is the reference — the method receives a copy of the reference, so it can mutate the object’s state, but it cannot make the caller’s variable point to a different object.

void increment(int x) { x = x + 1; } // does nothing to caller's variable

void move(Point p) { p.x = 10; } // mutates the caller's object

void reassign(Point p) { p = new Point(0, 0); } // does nothing to caller's reference

Null Handling

Only reference types can be null. Primitives are never null. This means:

Integer boxed = null;   // OK — Integer is a reference type
int primitive = null;   // Compile error — int cannot be null

Auto-unboxing a null Integer to int throws a NullPointerException.

String: A Special Reference Type

String is a reference type — it is a class — but it has several special behaviours:

  • Immutability: once created, a String object’s character sequence cannot change. Methods like toUpperCase() and substring() return new strings rather than modifying the original.
  • String pool: string literals (e.g. "hello") are interned — the JVM maintains a pool of unique string literals. String s1 = "hello"; String s2 = "hello"; results in s1 == s2 being true because they reference the same interned object.
  • Concatenation operator: the + operator is overloaded for strings. Any non-string operand concatenated with a string is converted via its toString() method.
  • StringBuilder: for repeated concatenation in loops, use StringBuilder to avoid creating many intermediate String objects.
String s1 = "hello";
String s2 = "hello";
String s3 = new String("hello");

s1 == s2;    // true — same interned object
s1 == s3;    // false — s3 is a new object, not from the pool
s1.equals(s3); // true — same character sequence