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 Field | Static Field | |
|---|---|---|
| Number of copies | One per object | One per class |
| Where stored | Inside the heap object | In the class’s metadata (method area) |
| When allocated | When new creates an object | When the class is loaded by the JVM |
| Lifetime | Lives as long as the object is reachable | Lives as long as the class is loaded (typically the whole program) |
| Access | object.field | ClassName.field |
| this available? | Yes | No — 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.