JAVA / CLASSES, CONSTRUCTORS AND ENCAPSULATION
static fields and methods shared across instances
Decide when data belongs to the class rather than an object, and use static fields and methods without creating hidden global state.
What you will learn
- Add a shared instance counter with a static field and read it through the class name
- Explain why static methods have no this and cannot read instance fields directly
- Choose static factory methods when two constructor signatures would collide
- Recognise mutable static state as global state shared across objects and threads
Understanding static fields and methods shared across instances
A field declared static belongs to the class, not to any object made from it. The JVM allocates one slot for it when it initializes the class, before the first new ever runs, and every instance that reads or writes it touches that same slot. Reading static as "there is exactly one of these, for as long as the class is loaded" is more useful than reading it as "belongs to no object".
That single-slot rule is why static methods have no this. A call like Ticket.issuedCount() names a class and supplies no receiver, so there is no object whose fields the method could read, and the compiler rejects any mention of an instance field inside it. The reverse direction is always legal: an instance method can read a static field, because there is only one of them and nothing to disambiguate. When a static method does need per-object data, you pass it an object and it reads fields through that reference.
Static earns its place in three situations: values every instance agrees on, bookkeeping that is genuinely about the class as a whole such as how many objects were created, and methods that depend only on their arguments or that produce instances rather than acting on one. Mutable static state is the risky case, because it is global state wearing a class name: it outlives every object, it survives between tests in the same JVM, and every thread sees the same slot with no synchronization. Default to instance fields until you can say why one shared copy is the correct model.
public class Main {
static class Ticket {
private static int issued = 0; // one slot for the whole class
private final int number; // one slot per object
Ticket() {
issued++; // shared bookkeeping
this.number = issued; // captured per object
}
String label() {
return "ticket " + number + " of " + issued;
}
static int issuedCount() {
return issued; // cannot mention number here: no this
}
}
public static void main(String[] args) {
System.out.println("issued before any object: " + Ticket.issuedCount());
Ticket first = new Ticket();
Ticket second = new Ticket();
Ticket third = new Ticket();
System.out.println(first.label());
System.out.println(second.label());
System.out.println(third.label());
System.out.println("issued now: " + Ticket.issuedCount());
}
}static attaches a member to the class itself, so exactly one copy exists, shared by every instance and alive before the first instance is created.
Worked examples
When the shared slot is set up
Shows that static fields and static blocks run once, at first active use of the class, not at program start.
public class Main {
static class Config {
static String env;
static int instances;
static {
System.out.println("Config class initialized");
env = "prod";
}
Config() {
instances++;
System.out.println("instance " + instances + " created");
}
}
public static void main(String[] args) {
System.out.println("main starts");
System.out.println("env=" + Config.env);
new Config();
new Config();
System.out.println("instances=" + Config.instances);
}
}Example explained
Line 1"main starts" prints first because Config is untouched at that point, so it has not been initialized yet.
Line 2Reading Config.env is the first active use of the class, which triggers the static block right before env=prod is printed.
Line 3The block never prints again: two constructor calls follow and class initialization happens exactly once.
Line 4instances++ in the constructor writes to the one class-level slot, so the objects report 1 then 2 instead of both reporting 1.
One slot seen through many references
Demonstrates that a static field ignores which reference you use, while an instance field does not.
public class Main {
static class Counter {
static int total;
int mine;
void hit() {
total++;
mine++;
}
}
public static void main(String[] args) {
Counter a = new Counter();
Counter b = new Counter();
a.hit();
a.hit();
b.hit();
System.out.println("a.mine=" + a.mine + " b.mine=" + b.mine);
System.out.println("a sees total=" + a.total + ", b sees total=" + b.total);
Counter.total = 0;
System.out.println("after reset a.total=" + a.total);
}
}Example explained
Line 1mine differs per object because each Counter object owns its own mine slot, incremented only by its own hit() calls.
Line 2a.total and b.total print the same 3 because both expressions compile down to the identical field access Counter.total.
Line 3Counter.total = 0 changes what a reports even though nothing about a was touched, which is what sharing means in practice.
Line 4Writing a.total to reach a static field is legal but misleading; the class-name form Counter.total states the truth.
Static factory methods
Shows why creation logic sometimes has to be static: it runs before any instance exists, and it can carry a name.
public class Main {
static class Temperature {
private final double celsius;
private Temperature(double celsius) {
this.celsius = celsius;
}
static Temperature fromCelsius(double c) {
return new Temperature(c);
}
static Temperature fromFahrenheit(double f) {
return new Temperature((f - 32) * 5 / 9);
}
double celsius() {
return celsius;
}
}
public static void main(String[] args) {
System.out.println("from F: " + Temperature.fromFahrenheit(212).celsius());
System.out.println("from C: " + Temperature.fromCelsius(21.5).celsius());
}
}Example explained
Line 1fromCelsius and fromFahrenheit must be static: they are called to produce a Temperature, so there is no instance to call them on.
Line 2Two constructors both taking a single double cannot coexist, while static methods can differ by name, which is the real reason to use them here.
Line 3The private constructor makes the two factories the only entry points, so every conversion goes through named, checkable code.
Line 4(f - 32) * 5 / 9 stays in double arithmetic because f is a double, so 212 becomes 100.0 rather than being truncated.
Important notes
Class initialization happens on first active use, not at program start, and if a static initializer throws you get ExceptionInInitializerError and the class stays unusable for the rest of the run.
static is only for members: local variables inside a method cannot be static, and static says nothing about immutability since a static int is still freely reassignable by any thread.
Common mistakes
Counting objects with an instance field: every new object gets its own copy initialized to 0, so the count is permanently stuck at 1 and the total is nowhere.
Hitting "non-static method cannot be referenced from a static context" in main and adding static to the field or method just to silence it: now all instances share one slot and the last write wins for everybody.
Treating a static id counter as a live population count: nothing ever decrements it, so it answers "how many were ever created", not "how many exist now".
Try it yourself
Change, predict, then run
Write a Robot class whose constructor takes a name and assigns each robot an id from a shared static counter, plus a static built() method returning the total. Create three robots, print each id and name, then print the total without going through any instance.
Open the Java workspaceCheck your understanding
Why can a static method not read an instance field of its own class directly?
- Because instance fields are private, and static code sits outside the object's encapsulation boundary
- Because instance fields do not exist until class initialization has finished running
- Because a static method is called through the class, so there is no particular object, and no this, to read the field from
- Because the compiler processes static members before constructors, so the field is not declared yet
Show answer
A static call names a class and supplies no receiver, so an expression like number is ambiguous: whose number? Give the method an object parameter and it can read that object's field, even a private one, which is why the privacy answer is wrong: private is enforced per class, not per object, so visibility was never the obstacle.