← Fundamentals
Guided lesson 7 questions · Java only

Java classes, fields, and static members

Which values live in each object, which live once in the class, and when the class sets them up.

An instance field exists once in every object. A static field exists once for the whole class, is shared by every object, and is set up when the program first uses the class. For each value, decide what it belongs to: one call, one object, the class, or another object, and declare it there.

Written for Java.

Before you begin

Why this matters

Every value in a Java program lives in one of a few places: a local in one call, a field in one object, or a static field in the class. Put a value in the wrong place and the program still compiles, but numbers repeat, totals grow between calls, or one object’s count shows on another. Knowing what each place shares lets you predict these results and choose the right place.

What this helps you reason about

  • Predict which values two objects share and which each one keeps.
  • Spot a field that keeps a value between calls where a local would start again.
  • Predict when a class sets up its static fields, and what a field declared lower down holds at that moment.

In Python and JavaScript, a program can add attributes to a class or to one object while it runs. A Java class declares, once and for all, which values live in each object and which live once in the class. Records, structs, and objects and Constructors and creating values followed the values in each object, and Methods, receivers, and this showed that a static method has no this. This lesson follows static fields themselves: who shares them, when they get their values, and the bugs they cause.

Already know this? The short version
  • An instance field exists in each object; a static field exists once in the class, is read as Bus.fleetSize, and the stepper lists it in every frame once the class is set up.
  • Fields start at 0, false, or null, so a field used as a working value keeps it between calls without any error; a local must be assigned before it is read (“variable total might not have been initialized”).
  • Number objects with a static counter and a final instance field; without static every object gets 1, and a static number field makes every object show the last one. A static method has no this, and using an instance field there gives “non-static variable … cannot be referenced from a static context”.
  • A class is set up once, at its first use (new, a static method, or a static field that is not a constant); static initializers then run from top to bottom, and constant variables are copied into the code that uses them.
  • The section “What goes wrong with static members in real programs?” covers static state shared between tests, a static list that only grows, a static final list that still changes, and an object made before the static fields it reads.

Go to the final check· Go to the practice tickets

Words in this lesson (8)
field
A variable declared in a class, outside every method. A static field belongs to the class; any other field belongs to each object of the class.
instance field
A field declared without static. Every object of the class has its own, created with the object by new.
static field
A variable declared with static in a class, outside every method. It belongs to the class, so there is one for the whole run, shared by every call.
default value
The value a field or array element starts with when none is given: 0 for numbers, false for boolean, and null for references. Local variables have no default value.
local variable
A variable declared inside a method. Each call creates its own, in its frame, and it is gone when the call returns.
access modifier
A word such as private or public before a field, method, or class that says which code may use it. private allows only code inside the same class.
static initializer
A block written static { … } in a class. It runs once, when the class is set up, together with the static fields’ starting values, from top to bottom.
constant variable
A final variable of a primitive type or String whose value is a constant expression, such as static final int SHELVES = 6. javac uses the value in place of the name.

Examples are focused snippets; imports, enclosing functions, or classes may be omitted. C examples use C11; Haskell examples use Haskell 2010. Expand the notes for language-specific depth.

01 / Object and class

What belongs to each object, and what belongs to the class?

Every object of a class has its own value for each instance field, as Records, structs, and objects showed. A field declared with static is different: it exists once, in the class itself, and every object shares it.

In the Bus class below, km is an instance field, so each new Bus gets its own km. fleetSize is a static field. There is one fleetSize for the whole program, and each constructor adds 1 to that one field. Code outside the class reads it through the class name, Bus.fleetSize, not through a bus.

The static field does not need any object. It is created when the program first uses the class Bus, and it is there even before the first bus.

Main.java
class Bus {
    // one for the whole class
    static int fleetSize = 0;
    // one in each Bus
    int km = 0;

    Bus() {
        fleetSize++;
    }
}

An instance field exists in every object; a static field exists once, in the class, and every object shares it.

Coming from Python: In Python, self.count = self.count + 1 creates a new attribute on one object and hides the class attribute. In Java, fleetSize++ inside a constructor changes the one static field.

Precisely: reading a static field through an object

javac also accepts a.fleetSize when a is a Bus. It still reads the one class field, chosen from the declared type of a, and it works even when a is null. Write Bus.fleetSize so that readers see the field is shared. JLS 8.3.1.1 describes static fields.

Experiment

Make two buses that share one static field

A static field is created once, when the class is set up, and lives in the class. An instance field is created inside each object by new. Bus.fleetSize names the class’s field; without static there is no such field, only one fleetSize in each Bus, so Bus.fleetSize means nothing.

Why it matters Read a static field through the class name, and expect one value however many objects exist.

Predict the three numbers that print. Then delete the word static on line 2 and predict whether the program still compiles.

class Bus {
    static int fleetSize = 0;
    int km = 0;

    Bus() {
        fleetSize++;
    }
}

class Main {
    public static void main(String[] args) {
        Bus a = new Bus();
        Bus b = new Bus();
        a.km = 120;
        b.km = 80;
        System.out.println(a.km + " " + b.km + " " + Bus.fleetSize);
    }
}
What to look for

At the first stop, main lists only args: the class Bus is not set up yet. The first new Bus() enters a frame named Bus.<clinit>. That is the JVM setting up the class, and it shows Bus.fleetSize = 0. From then on, every frame lists Bus.fleetSize, because the static field is not inside any object. The next frame, Bus.<init>, is the constructor. Its this shows Bus@ and an id, then {km: 0}, with no fleetSize inside it. The number after @ is the debugger’s id for an object, not an address. The constructor also stops on int km = 0;, because a field’s starting value is set for each new object. After fleetSize++, Bus.fleetSize is 1. The second new Bus() shows a second id with its own km, and Bus.fleetSize becomes 2; Bus.<clinit> does not run again. The run prints 120 80 2. After the edit, javac reports: error: non-static variable fleetSize cannot be referenced from a static context, with the mark under Bus.fleetSize in main.

Expected output

120 80 2

02 / Fields and locals

Which variables start with a value, and which must you assign?

Constructors and creating values showed that fields start at a default value: 0, false, or null. A local variable has no value until code assigns one, and javac checks every read of it. When a local might be read before it has a value, javac refuses the program:

error: variable total might not have been initialized

This difference matters when you choose where a value lives. A field never gets this check. A field used for a value that one call works with, such as a running total, compiles without any start value, and then keeps its value until the next call. The next call adds to the old total.

So a value that belongs to one call belongs in a local. Then each call starts again, and javac reports a forgotten start.

Main.java
static int total;

static int dayKm(int[] trips) {
    for (int trip : trips) {
        total += trip;
    }
    // the second call adds to the first
    return total;
}

A field keeps its value between calls and starts at 0 without a check; a local starts again in every call, and javac checks that it was assigned.

Precisely: definite assignment

Chapter 16 of the JLS defines the check: on every path to a read of a local, javac must see an assignment. It does not work out values, so int total; if (trips.length > 0) total = 0; followed by a read is refused even when every call has trips. Fields are not checked, except final fields, which every constructor must assign. JLS 4.12.5 lists the default values.

Experiment

Add up two days with a static total

A field keeps its value between calls, and javac never asks whether it was set: it starts at 0. A local variable is created again in each call and has no value until it is assigned, so javac checks every read of it. The edit makes total a local, and the check finds that total += trip reads it before any assignment.

Why it matters A value that one call works with belongs in a local, such as int total = 0;. Then every call starts again from 0, and javac tells you when you forget the start.

new int[] {40, 25} makes an array that holds 40 and 25. Predict both lines. Then delete the line static int total; and write int total; as the first line inside dayKm. Predict whether the program compiles.

class Main {
    static int total;

    static int dayKm(int[] trips) {
        for (int trip : trips) {
            total += trip;
        }
        return total;
    }

    public static void main(String[] args) {
        System.out.println(dayKm(new int[] {40, 25}));
        System.out.println(dayKm(new int[] {30}));
    }
}
What to look for

At the first stop, main lists Main.total = 0, although no line has assigned it: a static field starts at its default value. In dayKm, Main.total becomes 40, then 65, and the first line prints 65. The second call starts with Main.total still 65, so it adds 30 and prints 95, not 30. After the edit, javac refuses the program: error: variable total might not have been initialized, marked under total in total += trip;, and a second time under return total;.

Expected output

65
95

03 / Numbering objects

How does a class give each object its own number?

A common use of a static field is a counter that gives each new object its own number. The counter must be static, because every constructor must see the number the last one used. The number each object keeps must be an instance field, because each object has its own.

In the code below, number is final, so it can be assigned only once, in the constructor. Each constructor copies the counter into number and then moves the counter on.

The mistake goes both ways. Without static on nextNumber, each new object gets its own counter, which starts at 1, so every bus is number 1. With static on number, all buses share one number field, so every bus shows the last number given out.

For each value, ask "one per what?" One per call is a local. One per object is an instance field. One for the whole program is a static field. Sometimes the answer is one per some other object, such as one per depot; then the value is a field of that object.

Main.java
class Bus {
    static int nextNumber = 1;
    final int number;

    Bus() {
        number = nextNumber;
        nextNumber++;
    }
}

A counter shared by every new object is static; the number each object keeps is an instance field.

Coming from Kotlin: Kotlin has no static fields, so a counter goes in a companion object. In Java, the counter is a static field of the class itself.

Precisely: nextNumber++ and threads

nextNumber++ gives the old value, so number = nextNumber++; does both steps in one line. If several threads create objects at once, two constructors can read the same value; java.util.concurrent.atomic.AtomicInteger and its getAndIncrement method give each thread a different number.

Experiment

Number three buses with a static counter

The counter is one static field, so each constructor reads the value the last constructor left and moves it on. As an instance field, each new object gets its own counter that starts at 1, so every bus takes 1.

Why it matters A counter that must see every object is static. The number each object keeps is an instance field.

Predict the three numbers. Then delete the word static on line 2, so that nextNumber is an instance field, and predict again.

class Bus {
    static int nextNumber = 1;
    final int number;

    Bus() {
        number = nextNumber;
        nextNumber++;
    }
}

class Main {
    public static void main(String[] args) {
        Bus a = new Bus();
        Bus b = new Bus();
        Bus c = new Bus();
        System.out.println(a.number + " " + b.number + " " + c.number);
    }
}
What to look for

In Bus.<clinit>, the first stop shows Bus.nextNumber = 0, its default value, and the next stop shows 1. In each Bus.<init>, this shows {number: 0} until number = nextNumber; runs: a final field also starts at its default value. The first bus gets number 1, and Bus.nextNumber becomes 2. The second and third buses get 2 and 3. The run prints 1 2 3. After the edit, there is no Bus.<clinit> stop and no Bus.nextNumber in the list. Each new object shows {nextNumber: 0, number: 0}, then its own nextNumber 1, and takes 1 from it. At the end, a, b, and c each show {nextNumber: 2, number: 1}, and the run prints 1 1 1.

Expected output

1 2 3

04 / Static methods

What can a static method use?

A static method has no this, as Methods, receivers, and this showed. So it can use static fields and its own parameters, but not instance fields: there is no object to take them from. Using an instance field in a static method gives a compile error like the one for methods:

error: non-static variable km cannot be referenced from a static context

To use an object’s fields in a static method, pass the object in as a parameter, as in static int total(Bus[] fleet). An instance method can use both kinds of field.

A static field that any code can change is easy to break. An access modifier says which code may use a field or a method. private allows only code inside the same class, so private static int nextNumber can be changed only by Bus itself. public allows all code. With no modifier, code in the same package may use it.

Main.java
class Bus {
    private static int nextNumber = 1;
    int km;

    static int total(Bus[] fleet) {
        int sum = 0;
        for (Bus bus : fleet) {
            // an object is passed in, so bus.km works
            sum += bus.km;
        }
        return sum;
    }
}

A static method can use static fields and its parameters, but no instance field without an object.

Coming from C#: C# refuses a static member read through an object; Java accepts a.fleetSize and reads the class field.

Precisely: protected and packages

Java has four levels of access: private (the class), no modifier (the package), protected (the package and subclasses), and public (everyone). A package is a named group of classes; small programs like these are all in one unnamed package. JLS 6.6 defines access control.

Experiment

Compare the frame of an instance method with a static one

An instance method receives its object as this, so a field name alone means this.km. A static method receives no object, so km has no object to come from. Static fields need no object, so both kinds of method can use fleetKm.

Why it matters A static method can use static fields and its parameters. To use an object’s fields, pass the object in, or make the method an instance method.

Predict what prints. Then change the return line in report to return "fleet " + fleetKm + " bus " + km; and predict whether it compiles.

class Bus {
    static int fleetKm = 0;
    int km = 0;

    void drive(int distance) {
        km += distance;
        fleetKm += distance;
    }

    static String report() {
        return "fleet " + fleetKm;
    }
}

class Main {
    public static void main(String[] args) {
        Bus a = new Bus();
        Bus b = new Bus();
        a.drive(30);
        b.drive(12);
        System.out.println(a.km + " " + Bus.report());
    }
}
What to look for

Bus has no constructor in its code, so javac adds an empty one; each new Bus() stops on line 1, class Bus {, in a frame named Bus.<init>. In each call to drive, the frame lists distance, this (Bus@ and an id, then {km: …}), and Bus.fleetKm. The first call changes a’s km to 30, the second changes b’s km to 12, and Bus.fleetKm becomes 30, then 42. In report, the frame lists only Bus.fleetKm = 42: there is no this, because report was not called on a Bus. The run prints 30 fleet 42. After the edit, javac reports: error: non-static variable km cannot be referenced from a static context, with the mark under km.

Expected output

30 fleet 42

05 / Set-up and constants

When does a class set up its static fields?

The JVM sets up a class the first time the program uses it: when it creates an object of the class, calls one of its static methods, or uses one of its static fields. This is called class initialization. It happens once, and it can be long after the program started.

Setting up runs the starting values of the static fields and every static initializer, a block written static { … }, from top to bottom. The stepper shows it as a frame named <clinit>. Until a line runs, its field holds its default value.

A static final field of a primitive type or String whose value is known when compiling, such as static final int SEATS = 50;, is a constant variable, as Compile time and runtime showed. javac copies 50 into every line that reads Depot.SEATS. So reading a constant does not set up the class. By convention, constants have UPPER_CASE names.

A static final field that refers to an object, such as a list, is not a constant. final stops the field from being assigned again, but the list can still change, as Java final, primitives, and boxing showed.

Main.java
class Depot {
    static final int SEATS = 50;
    static int buses = 4;

    static {
        System.out.println("depot ready");
    }
}
// Depot.SEATS: 50 is copied in, Depot is not set up
// Depot.buses: sets up Depot first, once

A class is set up once, at its first use, from top to bottom; a constant is copied into the code that reads it.

Coming from Python: A Python class body runs when its module is imported. A Java class is set up at its first use, and a constant does not count as a use.

Precisely: the rules

JLS 12.4.1 lists what sets up a class: creating an object, calling a static method, assigning a static field, or reading a static field that is not a constant variable. JLS 8.7 describes static initializers. Because a constant is copied in, a class that reads Depot.SEATS keeps the old value until it is compiled again. Inside the class, a starting value that uses a static field declared lower down by its simple name, static int seats = buses * 50;, gives error: illegal forward reference (JLS 8.3.3).

Experiment

Find the moment the depot class is set up

The JVM sets up a class once, the first time the program uses it: a new object, a static method call, or a static field that is not a constant. Reading a constant variable is not a use, because the compiled code holds its value. Setting up runs the static field initializers and static blocks once, from top to bottom.

Why it matters Static set-up code runs once, at the class’s first use, which can be long after the program starts. A constant is copied into the code that uses it.

Predict the order of the four lines. Then delete the word final on line 2 and predict the order again.

class Depot {
    static final int SEATS = 50;
    static int buses = 4;

    static {
        System.out.println("depot ready");
    }
}

class Main {
    public static void main(String[] args) {
        System.out.println("seats " + Depot.SEATS);
        System.out.println("buses " + Depot.buses);
        System.out.println("buses " + Depot.buses);
    }
}
What to look for

The first println prints seats 50 without entering Depot: SEATS is a constant variable, and javac wrote 50 into main in its place. The second println enters a frame named Depot.<clinit>. There Depot.buses is 0, then 4, and the static block prints depot ready. Then main prints buses 4. The third println does not enter Depot.<clinit> again. The run prints seats 50, depot ready, buses 4, buses 4. After the edit, SEATS is a plain static field. The first println enters Depot.<clinit>, which starts with Depot.SEATS = 0 and Depot.buses = 0, and depot ready is printed before seats 50.

Expected output

seats 50
depot ready
buses 4
buses 4

06 / In production

What goes wrong with static members in real programs?

Each bug below follows from a rule in this lesson.

Static state that outlives a test. A static field lives for the whole run. Tests that create objects numbered from a static counter, or that add to a static list, see what earlier tests left there. A test then passes or fails depending on which tests ran before it. The usual patch is a static reset method that each test calls first.

A static list that only grows. static List<Bus> ALL = new ArrayList<>(), with ALL.add(this) in the constructor, keeps every bus ever made reachable for the whole run. The garbage collector can never reclaim them.

A static final list that still changes. static final List<String> ROUTES = new ArrayList<>(); cannot be assigned again, but any code can add to it or clear it. It is not a constant. List.of("10", "12") makes a list that refuses changes.

An object made before the fields it reads. A static field that creates an object of its own class runs the constructor during set-up. If it is declared above a static field that the constructor reads, the constructor sees that field’s default value, as the experiment below shows. Declare plain values first, or make them constants.

Main.java
class Fleet {
    // runs the constructor first
    static final Fleet CITY = new Fleet();
    // still 0 when the constructor reads it
    static int buses = 4;
    final int seats;

    Fleet() {
        seats = buses * 50;
    }
}

Static state lasts for the whole run and is set up in declaration order; give state an owner that lives only as long as it should.

Experiment

Make a fleet object while the class is still being set up

Static initializers run from top to bottom. CITY comes first, so its constructor runs before the line that sets buses, and reads the default value 0. javac does not complain, because buses is read in the constructor’s body, not in a static initializer above it. Nothing runs the constructor again later, so seats stays 0.

Why it matters A static field that creates an object of its own class must come after every static field its constructor reads.

Predict both numbers. Then move the line static int buses = 4; above static final Fleet CITY = new Fleet(); and predict again.

class Fleet {
    static final Fleet CITY = new Fleet();
    static int buses = 4;
    final int seats;

    Fleet() {
        seats = buses * 50;
    }
}

class Main {
    public static void main(String[] args) {
        System.out.println(Fleet.CITY.seats + " " + Fleet.buses);
    }
}
What to look for

Fleet.<clinit> starts with Fleet.CITY null and Fleet.buses 0. Line 2 runs new Fleet(), and the frame list shows Fleet.<init> above Fleet.<clinit>: the constructor runs while the class is still being set up. In the constructor, Fleet.buses is still 0, so seats stays 0. Back in Fleet.<clinit> at line 3, CITY shows Fleet@ and an id, then {seats: 0}. Only then does buses become 4. The run prints 0 4. After the edit, buses is already 4 when the constructor runs, this shows {seats: 200}, and the run prints 200 4.

Expected output

0 4

07 / Try it

Can you tell which values are shared?

One counter is static, one number is final in each object, and one count belongs to each ticket. Decide what each line leaves behind.

Main.java
class Main {
    public static void main(String[] args) {
        DayTicket x = new DayTicket();
        DayTicket y = new DayTicket();
        x.ride();
        x.ride();
        y.ride();
        System.out.println(y.code + " " + x.rides + " "
                + DayTicket.sold + " " + DayTicket.nextCode);
    }
}

class DayTicket {
    static int sold;
    static int nextCode = 100;
    final int code;
    int rides;

    DayTicket() {
        code = nextCode;
        nextCode += 10;
        sold++;
    }

    void ride() {
        rides++;
    }
}

A bus company sells two day tickets, and the first one is used for two rides. What is printed?

Reveal the reasoning

110 2 2 120. nextCode is static: x takes 100 and moves it to 110, and y takes 110 and moves it to 120. code is an instance field, so y keeps 110. rides is an instance field too, so x’s two rides do not reach y. sold is static and was never assigned a start value; it starts at 0, and each constructor adds 1, so it is 2.

Practice tickets

  1. JAVA-401 · Every baggage carousel shows the whole hall’s bag count → Free
  2. JAVA-402 · Each shift in the truck logbook includes the shifts before it → Sign in · Pro
  3. JAVA-403 · The second boat company’s tickets do not start at 1 → Sign in · Pro

Keep exploring

Functions as values and higher-order operations →

Primary references