skip to content

static

one copy per class vs one copy per object.

chapter
chapter 03.04
reading time
6 min read

a static field has one copy, owned by the class. an instance field has one copy per object. a static method runs without any object, so there's no this inside it.

static = "belongs to the class". that's the whole keyword.

the picture

think of an office. every employee has their own desk (instance fields: one per object). there's one coffee machine in the kitchen (a static field: one per class). anyone can use it, and if someone drinks the last cup, everyone notices.

1class Ticket {
2 static int issued = 0;
3 final int id;
4
5 Ticket() {
6 issued++;
7 id = issued;
8 }
9}
issued =

one issued, shared by all 3. each constructor bumps the same box, so ids come out 1, 2, 3...

class Ticket · one per JVMstatic issued3
Ticket
id1
Ticket
id2
Ticket
id3
press new Ticket() a few times, then flip to forgot static. with static, every constructor bumps the one counter in the class's box. without it, each object gets its own counter, bumps it once, and every ticket comes out as #1.

that "forgot static" bug compiles fine and looks right in review. it's one keyword.

Main.java
public class Main {
    static class Ticket {
        int issued = 0;   // oops: one per ticket
        final int id;
 
        Ticket() {
            issued++;
            id = issued;
        }
    }
 
    public static void main(String[] args) {
        Ticket a = new Ticket(), b = new Ticket(), c = new Ticket();
        System.out.println(a.id + " " + b.id + " " + c.id);
    }
}
output
1 1 1

walk through it

the real program, traced on a real JVM. the box tagged static fields belongs to the class, and it appears the moment the class is first used, before any Ticket exists.

Main.java next to run
  1. 1public class Main {
  2. 2 static class Ticket {
  3. 3 static int issued = 0;
  4. 4 final int id;
  5. 5
  6. 6 Ticket() {
  7. 7 issued++;
  8. 8 id = issued;
  9. 9 }
  10. 10 }
  11. 11
  12. 12 public static void main(String[] args) {
  13. 13 Ticket a = new Ticket();
  14. 14 Ticket b = new Ticket();
  15. 15 Ticket c = new Ticket();
  16. 16 System.out.println(a.id + " " + b.id + " " + c.id);
  17. 17 System.out.println(Ticket.issued);
  18. 18 }
  19. 19}
stdout
·
stack
main
no locals yet
heap
nothing allocated yet.

main is about to touch Ticket for the first time.

step 1/12
watch issued in the class's box tick 1, 2, 3 while each object's id is set once and never moves. note that no stack slot points at the class's box: static fields are reached through the class, not through a reference.

where does that box really live? in HotSpot, static fields sit inside the class's java.lang.Class object, on the heap. the class's metadata (its methods, the constant pool) lives in metaspace. more in the JVM's memory map.

static methods have no this

an instance method is called on an object, and that object arrives as this. a static method is called on the class, so there's no object, so there's no this. which means no instance fields and no instance methods, unless you hand it an object explicitly.

Main.java
public class Main {
    int count = 0;
 
    void greet() {
        System.out.println("hi");
    }
 
    public static void main(String[] args) {
        count++;
        greet();
    }
}
text
error: non-static variable count cannot be referenced from a static context
error: non-static method greet() cannot be referenced from a static context

the classic first-week error, because main is static. the fix is to make an object (new Main().greet()), or make the thing static if it really doesn't need an object. (java 25's instance main methods dodge this for small programs: void main() isn't static, so the launcher makes the object for you.)

predict the output
java
class Parent {
    static String who() { return "parent"; }
}
 
class Child extends Parent {
    static String who() { return "child"; }
}
 
void main() {
    Parent p = new Child();
    IO.println(p.who());
    Parent nobody = null;
    IO.println(nobody.who());
}

static init order can bite

static fields are initialized once, in source order, the first time the class is used. if a static initializer creates an instance of the same class, that constructor runs halfway through static init, and sees whatever hasn't been initialized yet as zero.

Main.java next to run
  1. 1public class Main {
  2. 2 static class Config {
  3. 3 static final Config INSTANCE = new Config();
  4. 4 static int defaultPort = 8080;
  5. 5
  6. 6 final int port;
  7. 7
  8. 8 Config() {
  9. 9 port = defaultPort;
  10. 10 }
  11. 11 }
  12. 12
  13. 13 public static void main(String[] args) {
  14. 14 System.out.println(Config.INSTANCE.port);
  15. 15 System.out.println(Config.defaultPort);
  16. 16 }
  17. 17}
stdout
·
stack
main
no locals yet
heap
nothing allocated yet.

first use of Config. before anything else, the class gets initialized.

step 1/8
step 3 is the whole bug: the constructor runs while defaultPort is still 0. the class's box fills in top to bottom, and the object copied the value before it got there.

two fixes: move INSTANCE below the fields it depends on, or make defaultPort a static final compile-time constant. constants are special: javac inlines 8080 straight into the constructor's bytecode, so nobody reads the field at runtime and the order stops mattering. same program with static final int defaultPort = 8080; prints 8080 twice.

what static is actually for

useexample
constantsstatic final int MAX_RETRIES = 3;
utility methods that need no stateMath.max(a, b), List.of(...)
factory methodsInteger.valueOf(42), Path.of("a.txt")
a nested class that doesn't need an outer objectstatic class Node (see nested classes)
main (classic form)the launcher calls it before any object exists
per-class bookkeepingcounters, caches. carefully, and thread-safely
keylines
  • static field: one copy, owned by the class. instance field: one per object.
  • static methods have no this, so they can't touch instance members directly.
  • statics initialize once, in source order, on first use of the class.
  • static methods hide, they don't override. the declared type picks.
  • static final constants: great. mutable static state: global variables with extra steps.