After this lesson, you will be able to:
- Explain why parallel arrays are fragile and what problem objects solve
- Distinguish between a class (blueprint) and an object (instance)
- Create objects using the
new keyword and call methods on them
- Draw a memory diagram showing references on the stack and objects on the heap
- Explain what
null means and why NullPointerException occurs
- Identify objects you have already used:
Scanner, String, File, ArrayList
Three Parallel Arrays and a Bug
You have 100 students. Each has a name, a GPA, and an ID. With the tools you have so far, you would write this:
String[] names = new String[100];
double[] gpas = new double[100];
int[] ids = new int[100];
names[0] = "Alice";
gpas[0] = 3.8;
ids[0] = 1001;
names[1] = "Bob";
gpas[1] = 3.2;
ids[1] = 1002;
It works. Index 0 is Alice across all three arrays. Index 1 is Bob. But the connection between names[0], gpas[0], and ids[0] exists only in your head. Java does not enforce it. And that is where things break.
Sort names alphabetically. The GPAs and IDs do not follow. Now names[0] is “Alice” but gpas[0] still holds whatever was there before the sort — maybe Bob’s GPA. Delete a student from one array but forget the other two. No exception. No compiler warning. Just silently corrupted data that produces wrong answers.
Want to pass a student to a method? You pass three separate values:
public static void printStudent(final String name, final double gpa, final int id) {
System.out.println(name + " (ID: " + id + ", GPA: " + gpa + ")");
}
printStudent(names[0], gpas[0], ids[0]);
Add an email field later and every method signature, every call site, and every piece of code that touches students must change. The code does not match the concept. You think “a student” as one thing, but the code stores it as three disconnected arrays held together by hope and careful indexing.
From CSCD 110: In Python, you might have used parallel lists the same way — names = [], gpas = [], ids = []. Python also lets you use dictionaries or tuples to bundle data, but it has no way to enforce that a “student” always has exactly three fields of specific types. Java classes give you that enforcement at compile time.
The Fix: Bundle Data into Objects
An object is a single unit that packages related data and related behavior together. Instead of three arrays, you define a Student class and create one array of Student objects:
public class Student {
private String name;
private double gpa;
private int id;
public Student(final String name, final double gpa, final int id) {
this.name = name;
this.gpa = gpa;
this.id = id;
}
public String getName() { return this.name; }
public double getGpa() { return this.gpa; }
public int getId() { return this.id; }
}
Now the usage is clean:
Student[] roster = new Student[100];
roster[0] = new Student("Alice", 3.8, 1001);
roster[1] = new Student("Bob", 3.2, 1002);
System.out.println(roster[0].getName()); // "Alice"
Sort the array and each student’s name, GPA, and ID move together — they are part of the same object. Delete one element and the entire student is gone. Pass a student to a method with one parameter instead of three. Add a field later and only the Student class changes; every method that accepts a Student automatically has access to the new data.
Key Insight: Data that belongs together should be stored together. Objects solve the fragmentation problem by bundling fields and methods into a single, self-contained unit.
Classes vs. Objects: Blueprints vs. Instances
These two terms are the foundation of everything this week.
A class is a blueprint. It describes what fields an object will have and what methods it can perform. You write the class once. It defines a new type — just like int and String are types, Student is a type you created.
An object (also called an instance) is a real thing built from that blueprint. Each object has its own copy of the fields with its own values. You can create as many objects from one class as you want, and each is independent.
Think of it this way: an architect draws blueprints for a house. The blueprint describes rooms, plumbing, electrical. But you cannot live in a blueprint. When a crew builds a house from those blueprints, that house is an instance. They can build 50 houses from the same blueprint, each with its own paint color and furniture.
// One class (blueprint):
public class Student { ... }
// Three objects (instances), each with their own data:
Student alice = new Student("Alice", 3.8, 1001);
Student bob = new Student("Bob", 3.2, 1002);
Student charlie = new Student("Charlie", 3.5, 1003);
alice, bob, and charlie are three separate objects. They all came from the same Student blueprint, so they all have a name, a gpa, and an id. But Alice’s name is “Alice” and Bob’s name is “Bob” — each object holds its own values.
How many
Student objects exist after this code runs?
Student a = new Student("A", 3.0, 1);
Student b = new Student("B", 3.5, 2);
Student c = a;</p>
Two new expressions create two objects. The line Student c = a; copies the reference --- c points to the same object as a. No third object is created. There are two objects and three reference variables.
---
## The `new` Keyword and Object References
When you declare a primitive variable, the variable holds the value directly:
```java
int age = 25; // age IS the number 25
```
Object variables work differently. The variable does not contain the object itself --- it contains a **reference**, an arrow that points to the object somewhere else in memory:
```java
Student alice = new Student("Alice", 3.8, 1001);
// alice is NOT the Student -- alice is an arrow pointing TO the Student
```
The `new` keyword does four things:
1. **Allocates** memory on the heap for a new `Student` object
2. **Calls the constructor** to initialize the fields
3. **Returns a reference** (an arrow) to the new object
4. **Stores that reference** in the variable `alice`
Here is what memory looks like after creating two students:
```
Stack Heap
┌─────────────┐ ┌─────────────────────┐
│ alice: ──────┼─────────────►│ name: "Alice" │
│ │ │ gpa: 3.8 │
│ │ │ id: 1001 │
├─────────────┤ └─────────────────────┘
│ bob: ──────┼──────┐
│ │ │ ┌─────────────────────┐
└─────────────┘ └─────►│ name: "Bob" │
│ gpa: 3.2 │
│ id: 1002 │
└─────────────────────┘
```
The **stack** (left side) holds the reference variables. The **heap** (right side) holds the actual objects. Each variable on the stack is an arrow pointing to an object on the heap. This is the same model you saw with arrays in Lesson 2.1 --- arrays are also reference types that live on the heap.
### What Happens with Assignment
When you assign one object variable to another, you copy the **reference**, not the object:
```java
Student alice = new Student("Alice", 3.8, 1001);
Student copy = alice; // copy points to the SAME object
```
```
Stack Heap
┌─────────────┐ ┌─────────────────────┐
│ alice: ──────┼─────────────►│ name: "Alice" │
├─────────────┤ ┌───►│ gpa: 3.8 │
│ copy: ──────┼─────────┘ │ id: 1001 │
└─────────────┘ └─────────────────────┘
```
Both `alice` and `copy` point to the same object. Change the GPA through `copy` and `alice` sees it too. This is the same aliasing behavior you learned with arrays.
What is the key difference between
int x = 5; and
Student s = new Student("A", 3.0, 1);?</p>
AThere is no meaningful difference
Bx is stored on the heap; s is stored on the stack
Cx holds the value 5 directly; s holds a reference (arrow) to the object on the heap
Dx is mutable; s is immutable
Primitive variables hold values directly. Object variables hold references --- arrows that point to objects stored on the heap. This distinction is why == behaves differently for primitives and objects, and why assigning one object variable to another creates an alias rather than a copy.
---
## The `null` Reference
Sometimes a reference variable does not point to any object. Java provides the special value `null`:
```java
Student student = null; // the arrow points to nothing
System.out.println(student); // prints "null"
```
`null` means "no object here." You can later assign a real object:
```java
student = new Student("Alice", 3.8, 1001);
```
But if you try to call a method on `null`, Java throws a `NullPointerException`:
```java
Student student = null;
System.out.println(student.getGpa()); // NullPointerException!
```
Java tries to follow the arrow to call `getGpa()`, but the arrow points nowhere. There is no object to operate on.
> **The Trick:** When you see a `NullPointerException`, ask: "Why is this variable `null` when I expected it to have an object?" Usually you forgot to initialize it with `new`. If `null` is a legitimate possibility (a search that found nothing, for example), guard against it:
>
> ```java
> if (student != null) {
> System.out.println(student.getGpa());
> }
> ```
### `null` in Arrays of Objects
When you create an array of objects, every slot starts as `null`:
```java
Student[] roster = new Student[3];
// roster is [null, null, null] — no Student objects exist yet!
```
```
Stack Heap
┌─────────────┐ ┌──────┬──────┬──────┐
│ roster: ─────┼─────────────►│ null │ null │ null │
└─────────────┘ └──────┴──────┴──────┘
```
You must fill each slot with `new`:
```java
roster[0] = new Student("Alice", 3.8, 1001);
roster[1] = new Student("Bob", 3.2, 1002);
roster[2] = new Student("Charlie", 3.5, 1003);
```
> **Common Pitfall:** Creating an array of objects does NOT create the objects themselves. `new Student[3]` creates three reference slots, all set to `null`. You must call `new Student(...)` for each slot. Forgetting this is the number one source of `NullPointerException` when working with object arrays.
---
## Objects You Have Already Used
You have been working with objects since the first week of this course. Every time you used a `String`, a `Scanner`, a `File`, an `ArrayList`, or a `Random`, you were using an object.
| Class (Blueprint) | Data It Holds | Methods You Have Called |
|-------------------|---------------|------------------------|
| `String` | Text characters | `.length()`, `.toUpperCase()`, `.substring()` |
| `Scanner` | Input stream + position | `.nextLine()`, `.nextInt()`, `.hasNextInt()` |
| `File` | File path | `.exists()` |
| `ArrayList` | Dynamic list of items | `.add()`, `.get()`, `.remove()`, `.size()` |
| `Random` | RNG state | `.nextInt()`, `.nextDouble()` |
When you wrote `new Scanner(System.in)`, you told Java: "Create a new object from the `Scanner` blueprint and give it `System.in` as the input stream to read from." You have been a consumer of classes all quarter. Now you are going to learn to design your own.
---
## Complete Example: From Parallel Arrays to Objects
Here is the full transformation. First, the parallel-array version:
```java
import java.util.Scanner;
public class ParallelArrayDemo {
public static void main(final String[] args) {
String[] names = {"Alice", "Bob", "Charlie"};
double[] gpas = {3.8, 3.2, 3.5};
int[] ids = {1001, 1002, 1003};
// Print all students
for (int i = 0; i < names.length; i++) {
System.out.println(names[i] + " (ID: " + ids[i]
+ ", GPA: " + gpas[i] + ")");
}
// Find the student with the highest GPA
int bestIndex = 0;
for (int i = 1; i < gpas.length; i++) {
if (gpas[i] > gpas[bestIndex]) {
bestIndex = i;
}
}
System.out.println("Highest GPA: " + names[bestIndex]
+ " with " + gpas[bestIndex]);
}
}
```
Now the object version using the `Student` class from earlier:
```java
public class ObjectDemo {
public static void main(final String[] args) {
Student[] roster = new Student[3];
roster[0] = new Student("Alice", 3.8, 1001);
roster[1] = new Student("Bob", 3.2, 1002);
roster[2] = new Student("Charlie", 3.5, 1003);
// Print all students
for (int i = 0; i < roster.length; i++) {
System.out.println(roster[i].getName() + " (ID: "
+ roster[i].getId() + ", GPA: "
+ roster[i].getGpa() + ")");
}
// Find the student with the highest GPA
int bestIndex = 0;
for (int i = 1; i < roster.length; i++) {
if (roster[i].getGpa() > roster[bestIndex].getGpa()) {
bestIndex = i;
}
}
System.out.println("Highest GPA: " + roster[bestIndex].getName()
+ " with " + roster[bestIndex].getGpa());
}
}
```
The logic is the same. But the object version keeps each student's data bundled together. Sort the array and every field travels with its student. Add an email field and `ObjectDemo` does not need to change at all --- only `Student` does.
What happens when you run this code?
Student[] roster = new Student[5];
System.out.println(roster[0].getName());</p>
AIt prints an empty string
BIt prints "null"
CIt throws a NullPointerException
DIt does not compile
new Student[5] creates an array of 5 reference slots, all initialized to null. No Student objects exist yet. Calling .getName() on null throws a NullPointerException. You must assign an object to each slot with new Student(...) before calling methods on it.
---
## Why This Matters for CSCD 211
Objects are not just a topic for one week. They are the organizing principle of nearly every Java program you will write from now on. In CSCD 211, you will learn **inheritance** (one class building on another), **polymorphism** (treating different types through a common interface), and **design patterns** (proven recipes for structuring classes). All of that depends on understanding what objects are, how references work, and why bundling data with behavior matters.
For the rest of this quarter, every new concept --- `toString()`, `equals()`, `Comparable`, interfaces --- builds on the foundation you learned today. The mental model of references on the stack pointing to objects on the heap will explain nearly every surprising behavior you encounter.
---
## Quick Reference
| Concept | Key Point |
|---------|-----------|
| Class | A blueprint that defines fields and methods. Written once. |
| Object (instance) | A real thing built from a class, with its own field values. Created with `new`. |
| Reference | An arrow stored in a variable that points to an object on the heap. |
| `new` | Allocates an object on the heap, calls the constructor, returns a reference. |
| `null` | A reference that points to no object. Calling a method on `null` throws `NullPointerException`. |
| Parallel arrays | Fragile pattern where related data is split across separate arrays. Objects replace this. |
---
## Summary
Parallel arrays store related data in separate arrays linked only by index. They break when you sort, delete, or add fields. Objects solve this by bundling related data (fields) and behavior (methods) into a single unit.
A class is a blueprint; an object is an instance built from that blueprint with `new`. Object variables hold references (arrows) to objects on the heap, not the objects themselves. Assigning one object variable to another copies the reference, creating an alias. The special value `null` means "no object" --- calling a method on `null` throws `NullPointerException`.
You have been using objects all quarter: `Scanner`, `String`, `File`, `ArrayList`. Now you will learn to write your own classes from scratch.
**Next lesson:** Writing Your First Class --- fields, constructors, methods, and the `this` keyword.