Command Palette

Search for a command to run...

PHASE 4Beginner ~30 min· topic 1 of 12

Topic 4.1

Classes and Objects

In one line

A class is a blueprint that describes what data a kind of thing holds and what it can do. An object is one real thing built from that blueprint with new, living on the heap, reached through a reference.

Think of it like this

A cake recipe and the cakes. The recipe says every cake has a flavour, a size and candles, and that you can slice it. The recipe is not a cake: you can't eat it. Each time you bake, you get a new cake with its own flavour and its own candles. Eat a slice of one cake and the other cakes are untouched. In Java the recipe is the class, and each cake is an object.

Words you'll meet

New words in this topic, in plain English. Come back here whenever one feels fuzzy.

Class
A blueprint you write in code that describes a kind of thing: what data it holds and what it can do. It's also a new type you can use for variables.
Object
One real thing made from a class while the program runs. It lives in memory and has its own values for each field.
Instance
Another word for object. "An instance of Dog" means "one Dog object".
Field
A variable declared inside a class but outside any method. Each object gets its own copy (unless the field is static, Topic 4.6).
`new`
The keyword that asks the JVM to create a fresh object on the heap and run its constructor.
Reference
The value stored in a variable of class type. It tells the JVM where the object is, like a house address written on a card.
Heap
The big area of memory where all objects live. It's shared by the whole program and cleaned up by the garbage collector.
Dot operator (`.`)
How you reach inside an object: rex.name reads a field, rex.bark() calls a method on that object.

Step by step

01Write a class: the blueprint

A class has a name (by convention a noun in UpperCamelCase), and inside its braces you list fields and methods. Here Dog says: every dog has a name and an age, and every dog can bark().

Nothing runs yet. A class on its own is like a recipe card in a drawer. The compiler checks it and turns it into a Dog.class file of bytecode, but no dog exists until some code says new Dog().

Notice main lives in Main, and Dog sits in the same file without public. Java allows only one public top-level class per file, and its name must match the file name. Other top-level classes in the file are package-private (Topic 4.5).

Main.javawhole filejava
public class Main {
    public static void main(String[] args) {
        // no Dog exists yet
    }
}

class Dog {
    String name;   // field: each dog has its own name
    int age;       // field: each dog has its own age

    void bark() {  // method: something every dog can do
        System.out.println(name + " says woof");
    }
}

02Create objects with new

new Dog() does four things in order: it makes sure the Dog class is loaded, allocates enough heap memory for one dog's fields, sets every field to its default (null and 0), and runs the constructor. Then it returns a reference to the new object.

Dog rex = new Dog(); stores that reference in the local variable rex. The variable lives in main's stack frame (Topic 3.11); the object lives on the heap.

You then use the dot operator to set fields and call methods. rex.bark() means "run the bark code, with rex's object as the dog doing the barking".

Main.javawhole filejava
Dog rex = new Dog();
rex.name = "Rex";
rex.age = 3;

Dog bella = new Dog();
bella.name = "Bella";
bella.age = 5;

rex.bark();    // Rex says woof
bella.bark();  // Bella says woof
terminal
$ javac Main.java && java Main
── expected output ──
Rex says woof
Bella says woof

03Picture the memory: stack holds references, heap holds objects

Two new calls means two separate objects on the heap, each with its own name and age. The variables rex and bella on the stack are small: each holds only a reference (typically 4 or 8 bytes).

The name field itself is a reference too, pointing to a String object elsewhere on the heap. Objects very often point to other objects; that web of references is your program's data.

Picture the memory: stack holds references, heap holds objecdiagram
Rendering diagram…

04Copying a variable copies the reference, not the object

Dog alias = rex; does not create a dog. It copies the reference stored in rex into alias. Now two variables point to one object.

Change the name through alias, and reading through rex shows the change, because there's only one dog. This is the same rule you met in Topic 3.3 (Java is always pass-by-value): the value being copied is the reference.

== compares references. rex == alias is true (same object). A brand-new dog with the same name and age is a different object, so == says false.

Main.javawhole filejava
Dog alias = rex;          // no new object
alias.name = "Rexy";
System.out.println(rex.name);        // Rexy

Dog twin = new Dog();
twin.name = "Rexy";
twin.age = 3;
System.out.println(rex == alias);    // true  - same object
System.out.println(rex == twin);     // false - different objects

05Default values for fields, but not for locals

When new allocates an object, the JVM zeroes the memory first. That's why an unset int field reads 0, a boolean reads false and a reference field reads null. Zeroing happens before any of your code runs, so a field can never contain leftover garbage.

Local variables are different. They live in the stack frame and the compiler's definite assignment rules (JLS chapter 16) require you to assign them before reading. Dog d; d.bark(); is a compile error: variable d might not have been initialized.

This difference is deliberate: fields can be read from many methods in any order, so the JVM guarantees a safe starting value. Locals are visible in one method, so the compiler can prove you set them, and an unset local is almost always a bug.

06What the compiler and JVM actually do for new

new Dog() compiles to three bytecode instructions: new Dog (allocate and zero the memory), dup (keep a copy of the reference on the operand stack), and invokespecial Dog.<init>()V (run the constructor on it). You can see them with javap -c Main.

On HotSpot, allocation is usually very cheap: each thread has a private chunk of the heap called a TLAB (thread-local allocation buffer), and allocating is mostly just bumping a pointer. Creating many small, short-lived objects is normal Java style, not a performance sin.

Every object also carries a small header before its fields: a mark word (used for locking, hashing and the garbage collector) and a pointer to its class. That class pointer is how rex.getClass() and method dispatch know what kind of object it is.

terminal
$ javap -c Main | findstr /c:"new" /c:"dup" /c:"invokespecial"
── expected output ──
0: new #7 // class Dog
3: dup
4: invokespecial #9 // Method Dog."<init>":()V

Try it yourself

  1. 1

    Add a third dog

    In "Two dogs, one class", create Dog max with age 1. Before running, predict the full output, then run and check. Call max.birthday() twice and print his age.

  2. 2

    Predict the alias

    In "References: alias vs a new object", add twin = rex; before the == lines. Predict both == results, then run. Explain why the second line changed.

  3. 3

    Look at the bytecode

    Compile the first example locally and run javap -c Main. Find the new, dup and invokespecial instructions for each dog.

    terminal
    $ javac Main.java
    javap -c Main
    ── expected output ──
    ...
    0: new #7 // class Dog
    3: dup
    4: invokespecial #9 // Method Dog."<init>":()V
    7: astore_1
    ...

Code & diagrams

Two dogs, one class New tab

Each object has its own fields: Rex's birthday doesn't change Bella's age.

Sign in to run this example in your browser.

Expected output

Rex says woof
Bella says woof
Rex is now 4
Bella is still 5
References: alias vs a new object New tab
Sign in to run this example in your browser.

Expected output

rex.name   = Rexy
rex == alias: true
rex == twin:  false
rex still there: Rexy
Default field values New tab
Sign in to run this example in your browser.

Expected output

int:     0
double:  0.0
boolean: false
char is zero: true
String:  null
array:   null
What javap shows for a classtext

The compiler added a constructor Dog() you never wrote: the default constructor (Topic 4.3).

> javac Main.java
> javap Dog
Compiled from "Main.java"
class Dog {
  java.lang.String name;
  int age;
  Dog();
  void bark();
  void birthday();
}

Break it on purpose

Errors are the best teachers. Make each change, read the error, guess what went wrong, then reveal the answer.

Break #1

Forget the new keyword

Write Dog d = Dog(); instead of Dog d = new Dog();.

terminal
$ javac Main.java
── what you'll see ──
Main.java:3: error: cannot find symbol
Dog d = Dog();
^
symbol: method Dog()
location: class Main
1 error

Break #2

Call a method through a null reference

Write Dog rex = null; and then rex.bark();.

terminal
$ javac -g Main.java && java Main
── what you'll see ──
Exception in thread "main" java.lang.NullPointerException: Cannot invoke "Dog.bark()" because "rex" is null
at Main.main(Main.java:5)

Myth vs fact

Myth

A variable of type Dog contains the dog.

Fact

It contains a reference to a Dog object on the heap. Assigning it to another variable copies the reference, so both point to the same object.

Myth

Each object has its own copy of the methods.

Fact

Method code exists once per class (in the JVM's metaspace). Each object only stores its fields plus a small header; methods find the right object through this.

Myth

Creating objects is slow, so avoid new.

Fact

On HotSpot, allocation in a thread-local buffer is a pointer bump, and short-lived objects are cheap to collect. Write clear code first; measure before avoiding allocation.

Pro corner

Extra depth for experienced readers. New to this? Skip it for now and come back later.

  • ▸

    Object size on 64-bit HotSpot with compressed class pointers (the default): a 12-byte header (8-byte mark word + 4-byte class pointer), then fields, rounded up to a multiple of 8 bytes. A Dog with one reference and one int is 12 + 4 + 4 = 20, padded to 24 bytes. JDK 24 added experimental compact object headers (8 bytes), made a product option in JDK 25. Measure real layouts with the JOL tool (org.openjdk.jol).

  • ▸

    The JVM may not even allocate on the heap: after JIT compilation, escape analysis can prove an object never leaves a method and replace it with plain local values (scalar replacement). This is why micro-benchmarks of new are misleading without JMH.

  • ▸

    In single-file source mode (java Main.java, Java 11 to 21), the launcher runs main in the first top-level class in the file. If Dog comes first you get error: can't find main(String[]) method in class: Dog. Put Main first. (Java 22+ picks the class matching the file name.)

  • ▸

    Classes are loaded lazily: Dog.class is read, verified and initialised the first time it's actually needed (for example the first new Dog()), not when the program starts. Topic 0.8 and Phase 13 go deeper into class loading.

Remember this

  1. 1

    A class declares a new type. It lists fields (the data each object holds, such as name and age) and methods (the actions each object can perform, such as bark()). Writing a class creates no objects at all; it only teaches the compiler a new shape.

  2. 2

    An object (also called an instance) is created at run time with the new keyword, for example new Dog(). The JVM finds free memory on the heap, sets every field to its default value, runs the constructor, and hands back a reference: the address-like value that lets you reach the object.

  3. 3

    A variable of a class type, such as Dog rex, never holds the object itself. It holds a reference to it. That's why Dog b = rex; copies the reference, not the dog: both variables now point to the same single object, and a change made through one is visible through the other.

  4. 4

    Each object has its own copy of every (non-static) field. Two Dog objects can have different names and ages at the same time. Methods, on the other hand, exist once per class; every object shares the same method code and the method knows which object it's working on through a hidden reference called this (Topic 4.4).

  5. 5

    Fields that you don't set get default values: 0 for numbers, false for boolean, '\u0000' for char, and null for references. Local variables inside methods get no default, which is why the compiler insists you assign them first.

  6. 6

    == on two references asks "are these the same object?", not "do they hold the same data?". Two separate new Dog() objects with identical fields are still different objects. Comparing by content is the job of equals, which you'll write yourself in Topic 4.8.

Explain it without notes

01

What is the difference between a class and an object? Use an analogy and then the precise Java terms.

02

What exactly happens, step by step, when the JVM executes Dog d = new Dog();?

03

Dog a = new Dog(); Dog b = a; b.name = "Max"; What is a.name and why?

04

Why do fields get default values while local variables must be assigned before use?

Practice

01

Write a class Book with fields title (String) and pages (int) and a method describe() that prints <title> has <pages> pages. Create two books and call describe() on both.

02

Write a class Counter with an int value field and a method increment(). Create two counters, increment the first three times and the second once, and print both values.

03

Create one Book, store it in two variables x and y, change pages through y, and print x.pages and x == y.

Trade-offs

  • ↔

    Modelling everything as small objects makes code readable and matches the problem, at the cost of some memory per object (header + padding). For millions of tiny values in a hot loop, a primitive array can be far smaller; choose objects first and optimise only where a profiler points.

  • ↔

    Public fields set from outside (as in this topic's first examples) are quick to write but let any code put an object into a nonsense state, like a negative age. Topics 4.3 and 4.5 fix that with constructors and private fields.

Done when you can

  • Done when you can explain class vs object with an analogy and with heap/stack terms.

  • Done when you can write a class with fields and methods and create several independent objects from it.

  • Done when you can predict the result of copying a reference and of == between two objects.

  • Done when you can list the default value of each field type and explain why locals have none.

  • Done when you can describe what new does in the JVM (allocate, zero, construct, return reference).