skip to content
everything.oop.classesAndObjects

classes and objects

a class is a blueprint, new stamps out an object. what's actually in memory.

chapter
chapter 03.01
reading time
8 min read

a class is a blueprint: which fields an object has and what its methods do. new uses that blueprint to carve a fresh object out of the heap, with its own copy of every field, runs the constructor on it, and hands you a reference.

one class, as many objects as you want. the class exists once.

the picture

you know how everyone says "a class is a blueprint" and then never shows you the house? here's the house.

think of a cookie cutter (the class) and the cookies (objects). the cutter decides the shape, every cookie gets its own dough (its own fields), and you can bite one cookie without the others noticing. what you're holding in your hand is never the cookie itself, it's a note saying which plate it's on (the reference).

class Dognot loaded yet
fieldsString name, int age · each object gets its ownmethodsDog(..), birthday() · shared by allobjects0
stamp =
call =
no dogs yet
stack
main
no locals yet
heap
nothing allocated yet.

nothing yet. press new.

press new and watch the four phases. the object exists with null and 0 in every field before the constructor runs. then call birthday() on one dog: one method, run against one object. purple is the class, blue is a reference, amber is the object being built.

notice what got copied and what didn't. every dog has its own name and age (that's what "instance field" means: one per instance). but birthday() wasn't copied into anybody. the code lives once, in the class, and runs against whichever object you call it on.

a class, piece by piece

Dog.java
class Dog {
    String name;                // fields: the state.
    int age;                    // every Dog gets its own copy
 
    Dog(String name, int age) { // constructor: fills in a fresh object
        this.name = name;
        this.age = age;
    }
 
    void birthday() {           // method: behaviour, shared by every Dog
        age++;                  // short for this.age++
    }
}
field
a variable that belongs to an object. it lives inside the object, on the heap, for as long as the object does.
constructor
the code that runs right after new allocates the object. same name as the class, no return type. the full story is in constructors and init order.
method
a function that belongs to the class. it gets a hidden first parameter, this, the object it was called on.
object
one instance of a class. a chunk of heap with a small header and the fields.

walk through it

two dogs, one birthday, and a second arrow to the same dog. press step and watch which box each line touches.

Main.java next to run
  1. 1class Dog {
  2. 2 String name;
  3. 3 int age;
  4. 4
  5. 5 Dog(String name, int age) {
  6. 6 this.name = name;
  7. 7 this.age = age;
  8. 8 }
  9. 9
  10. 10 void birthday() {
  11. 11 age++;
  12. 12 }
  13. 13}
  14. 14
  15. 15void main() {
  16. 16 Dog rex = new Dog("rex", 3);
  17. 17 Dog luna = new Dog("luna", 3);
  18. 18 rex.birthday();
  19. 19 Dog same = rex;
  20. 20 IO.println(rex.age + " " + luna.age);
  21. 21 IO.println(same == rex);
  22. 22}
stdout
·
stack
main
no locals yet
heap
nothing allocated yet.

main starts. no Dog exists yet, just the class.

step 1/13
steps 2 and 6: a brand new object, fields still zeroed, outlined in amber. step 9: birthday() gets a frame whose this is rex's arrow. that's the whole trick behind methods.

see? rex isn't a dog. it's an arrow to a dog. same = rex doesn't clone anything, it copies the arrow (the same rule as stack and heap: = copies what's in the slot).

methods live in the class

here's the thing: if every object carried its own copy of every method, a million dogs would mean a million copies of birthday(). instead, the method is stored once, and every call passes the object in as this:

text
rex.birthday()      is really      Dog.birthday(this = rex)

that's literally what the bytecode does: invokevirtual Dog.birthday, with the reference pushed first. inside the method, age with no prefix means this.age. you write this. yourself only when a local shadows a field, like the constructor's this.name = name (field on the left, parameter on the right).

predict the output
java
Dog rex = new Dog("rex", 3);
Dog luna = new Dog("luna", 3);
rex.birthday();
IO.println(rex.age + " " + luna.age);

same fields, different objects

two objects built from the same blueprint with the same values are still two objects. == on references asks "are these the same arrow target?", not "do they look alike?".

predict the output
java
Dog a = new Dog("rex", 3);
Dog b = new Dog("rex", 3);
IO.println(a == b);
IO.println(a.equals(b));

every object has an identity: it's this object, not merely one that looks the same. that's why printing a plain Dog gives you something like Dog@58fdd99: a class name and a hash of the identity, not the contents.

what one object costs

an object isn't just its fields. the JVM puts a small header in front: a mark word (hash, lock bits, GC age) and a pointer to the class, which is how a dog knows it's a Dog.

object =
headers =
081624

hover a slice. purple is the JVM's own bookkeeping, blue is a reference, grey is a primitive, hatched is padding.

24 bytes
pick an object and a header mode. sizes are measured, not guessed: jdk 25 on an arm mac, allocated bytes per new over a million allocations. compact headers (JEP 519) are a product feature in 25 but still off by default.

so an empty new Object() is 16 bytes, and a Dog with one reference and one int is 24. turn on compact headers and they drop to 8 and 16. that's the reason a million tiny objects can hurt more than you'd think, and why object layout gets its own chapter.

keylines
  • a class is the blueprint. it exists once.
  • new allocates an object with every field zeroed, runs the constructor, and returns a reference.
  • every object gets its own fields. methods are shared and get the object as this.
  • a variable of a class type holds an arrow, never the object.
  • == on objects compares identity. two lookalikes are still two objects.