Java Non-Primitive Data Types

In the last lesson you learned about Java primitive data types like int, double, and boolean, which hold one simple value. But a real program needs names, lists, and whole objects too. That is where non-primitive types (also called reference types) come in.

🤔 What is a non-primitive type?

A primitive holds its value directly. A non-primitive holds a reference instead. The key difference:

  • A primitive: int age = 25 puts the number 25 right inside the box.
  • A reference: the variable holds an address, not the data.
  • The real data is an object, and it lives in memory called the heap.
  • Picture a sticky note with a home address. The note is not the house. It tells you where to go.
  • Many notes can carry the same address, so they all point at one house.

The common non-primitive types you will meet:

  • String holds text, like a name or a message.
  • Arrays hold many values of the same type in one place.
  • Classes are objects you build yourself, like a Student or a Car.
  • Interfaces describe what an object can do (studied later).
  • Wrapper classes like Integer and Double wrap a primitive inside an object.

Naming hint: primitive names are lowercase (int, double); reference names start with a capital (String, Integer, your own Student). That capital is your clue.

📦 The variable holds a reference, not the value

Here is a primitive and a reference side by side.

int score = 90; // the value 90 sits inside score
String name = "Alex"; // name holds a reference to the text "Alex"
System.out.println(score);
System.out.println(name);

What is happening:

  • score holds the number 90 right inside the variable.
  • name does not hold the letters. It holds an address to the text on the heap.
  • Printing name makes Java follow the address, find the text, and show it.

Output

90
Alex

You never see the address, and that is fine. The picture to keep: a reference points at an object, it does not contain the object.

🕳️ null and default values

A reference can also point at nothing. That “nothing” has a name in Java: the keyword null.

String message = null; // points at nothing right now
System.out.println(message);

A null reference is a sticky note with no address on it. There is no object on the other end yet.

Output

null

This also explains default values:

  • A primitive field defaults to 0, false, and so on.
  • A reference field defaults to null, because Java has no object to point it at yet.
public class Defaults {
static int count; // a primitive field, defaults to 0
static String label; // a reference field, defaults to null
public static void main(String[] args) {
System.out.println(count);
System.out.println(label);
}
}

The number starts at 0. The reference starts at null, meaning “no object yet”.

Output

0
null

null says “no value yet”. It is also the source of one of the most common Java errors (see the mistakes section). For now: a reference either points at an object or is null.

🔗 Two references, one object (aliasing)

The idea that sets reference types apart:

  • Two variables can hold the same address, so both point at one object. This is aliasing.
  • Change the object through one variable, and the change shows through the other.
  • Primitives never do this. Copying a primitive copies the value, so the two stay separate.

Here is the primitive case first.

int a = 5;
int b = a; // b gets its own copy of the value 5
b = 99; // changing b does not touch a
System.out.println(a);
System.out.println(b);

b = a copied the number 5 into b. Two separate boxes now, so changing b leaves a alone.

Output

5
99

Now the reference case, using an array (a reference type whose contents we can change).

int[] first = {1, 2, 3}; // first points at an array object
int[] second = first; // second copies the reference, not the array
second[0] = 99; // change the object through second
System.out.println(first[0]);
System.out.println(second[0]);

What is happening:

  • second = first did not make a new array. It copied the address.
  • first and second now point at the same array on the heap.
  • Setting second[0] to 99 changed that one shared object, so first[0] shows it too.

Output

99
99

That is aliasing. Both names lead to one house, so a change through one door is seen through the other. This is not a bug. It is how reference types work. Whenever you assign one reference to another, you copy the address, not the object.

The key difference

Assigning a primitive copies the value, so the two are independent. Assigning a reference copies the address, so both variables share one object.

⚖️ Comparing with == versus .equals()

Two ways to compare, asking different questions:

  • == asks “same object?” It compares the addresses.
  • .equals() asks “same contents?” It compares what is inside.
  • For primitives you only have ==, and it compares values, which is what you want.
  • For reference types the difference matters a lot.

Here is a String example.

String x = new String("hello");
String y = new String("hello");
System.out.println(x == y); // same object? no, two separate objects
System.out.println(x.equals(y)); // same contents? yes, both say "hello"

Two separate String objects made with new, both holding "hello":

  • They are two houses that look the same inside.
  • == is false because the addresses differ.
  • .equals() is true because the contents match.

Output

false
true

To check whether two objects mean the same thing, use .equals(). Use == only when you really mean “the very same object”. For text, almost always use .equals().

🛠️ A worked example

One small program. Alex and Riya look at the same shopping list (two references to one array), then compare strings.

public class SharedList {
public static void main(String[] args) {
String[] alexView = {"milk", "bread", "eggs"};
String[] riyaView = alexView; // same list, not a copy
riyaView[1] = "butter"; // Riya edits the shared list
System.out.println("Alex sees: " + alexView[1]);
System.out.println("Riya sees: " + riyaView[1]);
String greeting = "hi";
String typed = new String("hi");
System.out.println("== check: " + (greeting == typed));
System.out.println("equals check: " + greeting.equals(typed));
}
}

Line by line on the ideas that matter:

  • riyaView = alexView copies the reference, so both names point at one array.
  • Riya changes index 1 to "butter", and because the array is shared, Alex sees the change too.
  • greeting == typed is false, because new String(...) made a separate object with its own address.
  • greeting.equals(typed) is true, because both hold the text "hi".

Output

Alex sees: butter
Riya sees: butter
== check: false
equals check: true

Both see "butter" because there was only ever one list. The string checks show why .equals() is the safe choice for contents.

📋 Primitive versus reference at a glance

The whole contrast in one table. If you remember one thing, let it be this split.

Question Primitive type Reference type
What the variable holds The actual value A reference (address) to an object
Where the data lives Right in the variable In an object on the heap
Examples int, double, char, boolean String, arrays, classes, Integer
Name starts with lowercase letter capital letter
Default value 0, 0.0, false, and so on null
Can it be null? No Yes
Copying a variable copies The value (independent) The address (shared object)
How to compare == compares values .equals() compares contents

⚠️ Common Mistakes

Two slip-ups catch nearly everyone.

Calling a method on a null reference. A null reference points at no object. Call .length() on it and Java has no object to act on, so it stops with a NullPointerException. This is the single most common Java error.

// ❌ Avoid: name is null, so there is no object to call .length() on
String name = null;
System.out.println(name.length()); // throws NullPointerException
// ✅ Good: give it a real object, or check for null first
String name = "Alex";
if (name != null) {
System.out.println(name.length());
}

The fix: make sure a reference points at an object before you use it. A quick if (x != null) check guards against the crash.

Comparing object contents with ==. For reference types, == checks “same object?”, not “same contents?”. So comparing two strings with == can say false even when the text is identical. Use .equals() for contents.

// ❌ Avoid: == asks "same object?", which may be false even for equal text
String a = new String("yes");
String b = new String("yes");
if (a == b) {
System.out.println("match"); // this does NOT print
}
// ✅ Good: .equals() asks "same contents?"
if (a.equals(b)) {
System.out.println("match"); // this prints
}

✅ Best Practices

A few habits keep reference types safe:

  • Compare objects with .equals(). Save == for the rare case where you mean “the exact same object”.
  • Check for null first. A guard like if (x != null) stops the NullPointerException before it happens.
  • Assigning a reference shares the object. With b = a, both point at one object. For a separate copy, create a new object on purpose.
  • Prefer a real default over null. An empty string "" or empty list is safer than null, because it will not crash.
  • Let the capital letter remind you. A capitalised type name is a reference type: it can be null and usually wants .equals().

🧩 What You’ve Learned

The big ideas:

  • ✅ Non-primitive (reference) types include String, arrays, classes, interfaces, and wrapper classes.
  • ✅ A reference variable holds an address to an object on the heap, not the value itself.
  • ✅ The default value of any reference type is null, which means “points at no object”.
  • ✅ Assigning one reference to another shares the object, so a change shows through both. This is aliasing.
  • ✅ Use .equals() to compare contents and == only to ask “same object?”.
  • ✅ Using a null reference causes a NullPointerException, so check for null first.

Check Your Knowledge

Test what you learned. Pick an answer for each question, then click Check.

  1. 1

    What does a non-primitive variable actually hold?

    Why: A reference type holds the address of an object on the heap, not the value itself.

  2. 2

    What is the default value of a reference type field?

    Why: Reference type fields default to null, because there is no object to point at until you make one.

  3. 3

    How should you compare the contents of two String objects?

    Why: Use .equals() to compare contents. == only checks whether two variables point at the same object.

  4. 4

    If second = first for two arrays, then you change second[0], what happens to first[0]?

    Why: Assigning a reference copies the address, so both point at one array. A change through one shows through the other. This is aliasing.

🚀 What’s Next?

You now know how reference types store their data and how to compare them safely. Next we will look at literals, the fixed values you type straight into your code, like 25, 3.14, and "hello", and the small rules each kind follows.

Java Literals

Share & Connect