Skip to content
Part IA Michaelmas Term

Static Fields

Class-Level State

A static field belongs to the class itself, not to any particular instance. There is exactly one copy of the field, shared by all instances of the class (and accessible even when zero instances exist).

public class Spaceship {
    private static int shipsCreated = 0;
    private String name;

    public Spaceship(String name) {
        this.name = name;
        shipsCreated = shipsCreated + 1;
    }

    public static int getShipsCreated() {
        return shipsCreated;
    }
}

Every Spaceship constructor increments the same shipsCreated counter. The counter tracks a class-wide property: how many spaceships have been created. No individual spaceship owns it.

Accessing Static Fields

Static fields are accessed through the class name, not through an instance reference:

int count = Spaceship.getShipsCreated();   // preferred

Java also allows accessing static members through an instance reference (someShip.getShipsCreated()), but this is misleading — it suggests the field belongs to the instance. The compiler silently replaces the reference with the class name. Modern IDEs warn about this.

From within the class itself, you can use the bare field name (shipsCreated) or qualify with the class name (Spaceship.shipsCreated). Using the class name makes the static nature explicit.

Static vs Instance Fields

Instance FieldStatic Field
Number of copiesOne per objectOne per class
Where storedInside the heap objectIn the class’s metadata (method area)
When allocatedWhen new creates an objectWhen the class is loaded by the JVM
LifetimeLives as long as the object is reachableLives as long as the class is loaded (typically the whole program)
Accessobject.fieldClassName.field
this available?YesNo — no instance context

Static Final Constants

A static final field with a compile-time constant value is Java’s mechanism for named constants:

public class Math {
    public static final double PI = 3.141592653589793;
    public static final double E = 2.718281828459045;
}

public class Configuration {
    public static final int MAX_CONNECTIONS = 100;
    public static final String DEFAULT_LANGUAGE = "en";
}

These are public because they are part of the class’s interface — they are intended to be read by external code. They are final because they are constants. They are static because there is no reason for each instance to carry its own copy of a fixed value.

The naming convention for static final constants is UPPER_SNAKE_CASE.

Static Counters (Shared Mutable State)

The shipsCreated example above shows a counter: a static field that changes over time. This is shared mutable state — every part of the program that creates a Spaceship implicitly modifies the counter.

Shared mutable static state is powerful but dangerous:

public class IdGenerator {
    private static int nextId = 1;

    public static int generateId() {
        int id = nextId;
        nextId = nextId + 1;
        return id;
    }
}

This works fine in a single-threaded program, but in a multi-threaded program, two threads calling generateId() simultaneously could read the same nextId value, both increment it to the same number, and return duplicate IDs. The fix involves synchronisation (the synchronized keyword or AtomicInteger) — beyond the scope of the Part IA course, but worth knowing as the motivation for minimising mutable static state.

Static Initialisation Blocks

Sometimes static field initialisation requires more than a simple expression. A static initialisation block runs once when the class is first loaded:

public class Config {
    public static final Map<String, String> defaults;

    static {
        defaults = new HashMap<>();
        defaults.put("host", "localhost");
        defaults.put("port", "8080");
        defaults.put("timeout", "30");
    }
}

The static { ... } block runs when the class is initialised, before any static field is accessed or any instance is created. Multiple static blocks run in source order.

Pitfalls and Traps

The Instance-Accessing-Static Illusion

Spaceship s = null;
int count = s.shipsCreated;   // COMPILES and works! Does not throw NullPointerException.

This is a trap. The compiler sees that shipsCreated is static and replaces s.shipsCreated with Spaceship.shipsCreated. The value of s (even null) is irrelevant. Never write this — it confuses readers.

Static State Survives Object Creation

Static fields persist across the program’s lifetime. If a test sets a static flag and does not reset it, subsequent tests may see the stale value. This is why good test frameworks run tests in isolation and why mutable static state is minimised in well-designed systems.

Shadowing

A local variable or parameter can shadow a static field:

public class Example {
    private static int value = 10;

    public void method(int value) {
        System.out.println(value);      // parameter, prints argument
        System.out.println(Example.value); // static field, prints 10
    }
}

Using the class name to qualify resolves the ambiguity.