☕ Java

static Keyword

The static keyword declares that a member belongs to the class itself rather than to any specific instance of the class. A static field is shared by all instances — there is exactly one copy in memory regardless of how many objects are created. A static method can be called on the class name without any object existing. Static members are initialised when the class is loaded into the JVM, before any object is ever created. Understanding the distinction between instance state (per-object) and class state (shared, static) is fundamental to designing correct and efficient Java programs.

static Fields — Shared Class-Level State

An instance field creates one copy per object. If you create a thousand BankAccount objects, each has its own balance field, completely independent of all others. A static field creates one copy per class — it is shared by every instance and by the class itself. Modifying a static field through any reference changes the single shared value. Static fields represent state that belongs to the class concept rather than to any individual object. The total number of objects created is a classic example — no single object owns that count, it is a property of the class as a whole. Similarly, a conversion constant like the value of pi belongs to a Math class concept, not to any particular Math object. Static fields are stored in a special area of the JVM's memory called the method area (also called metaspace in modern JVMs), separate from the heap where instance fields live. They are initialised to their default values when the class is loaded and then assigned their explicit values in order. They persist for the entire lifetime of the JVM process — they are never garbage collected as long as the class remains loaded. Because static fields are shared, they can cause threading problems when multiple threads access them without synchronisation. A static counter incremented by many threads simultaneously can produce incorrect counts due to race conditions. This is one reason to be thoughtful about mutable static fields — they are effectively global variables, with all the design and concurrency complications that implies.
Java
// ── Instance field vs static field: ──────────────────────────────────
public class BankAccount {
    // Instance field — each object has its OWN copy:
    private double balance;

    // Static field — ONE copy shared by all objects and the class:
    private static int totalAccountsCreated = 0;
    private static double totalDepositsAllAccounts = 0.0;

    public BankAccount(double initialBalance) {
        this.balance = initialBalance;
        totalAccountsCreated++;         // increment the shared counter
        totalDepositsAllAccounts += initialBalance;
    }

    public void deposit(double amount) {
        this.balance += amount;         // modifies THIS object's balance only
        totalDepositsAllAccounts += amount;  // modifies the ONE shared total
    }

    // Static method to read the static field:
    public static int getTotalAccountsCreated() {
        return totalAccountsCreated;
    }

    public static double getTotalDeposits() {
        return totalDepositsAllAccounts;
    }

    public double getBalance() { return balance; }
}

BankAccount acc1 = new BankAccount(1000.0);
BankAccount acc2 = new BankAccount(2000.0);
BankAccount acc3 = new BankAccount(500.0);

acc1.deposit(250.0);

System.out.println(BankAccount.getTotalAccountsCreated());  // 3
System.out.println(BankAccount.getTotalDeposits());         // 3750.0
System.out.println(acc1.getBalance());                      // 1250.0
System.out.println(acc2.getBalance());                      // 2000.0 — unchanged

// ── Static constants — the most common use of static fields: ──────────
public class MathConstants {
    public static final double PI      = 3.14159265358979;
    public static final double E       = 2.71828182845904;
    public static final double SQRT_2  = 1.41421356237310;
}

// Accessed on the class name — no object needed:
double circumference = 2 * MathConstants.PI * 5.0;

// ── Accessing static via instance reference — works but misleading: ───
BankAccount acc = new BankAccount(100.0);
System.out.println(acc.getTotalAccountsCreated());  // works, but misleading
System.out.println(BankAccount.getTotalAccountsCreated());  // preferred — clear

static Methods

A static method belongs to the class, not to any instance. It can be called on the class name without creating any object, and it does not receive a this reference. Because it has no this, a static method cannot directly access instance fields or call instance methods — it only has access to other static members, its own local variables, and whatever is passed to it as parameters. Static methods are the right choice for utility operations that do not need any object state: mathematical calculations, string parsing, factory methods that create objects, and validation functions that operate entirely on their parameters. The Java standard library's java.lang.Math class is the canonical example — Math.abs(), Math.sqrt(), and Math.max() are all static because computing them requires only the input values, not any object state. The inability to access instance members from a static method is not a limitation — it is a guarantee. It means you can call a static method at any time without worrying about whether an object exists or what its state might be. Static methods are predictable, easy to test (no object setup required), and naturally stateless when they have no side effects on static fields. One important and widely used pattern is the static factory method: a static method that creates and returns an object, often with a descriptive name that clarifies what kind of object is being created. Integer.valueOf(42), Optional.of(value), and LocalDate.of(2025, 5, 30) are all static factory methods. They have several advantages over constructors: they have names (unlike constructors), they can return a subtype, they can return a cached instance, and they can perform complex initialisation before returning.
Java
// ── Static method — no instance needed: ──────────────────────────────
public class StringUtils {

    // Static utility — operates only on its parameters:
    public static boolean isPalindrome(String text) {
        if (text == null) return false;
        String cleaned = text.toLowerCase().replaceAll("[^a-z0-9]", "");
        String reversed = new StringBuilder(cleaned).reverse().toString();
        return cleaned.equals(reversed);
    }

    public static String capitalise(String text) {
        if (text == null || text.isBlank()) return text;
        return Character.toUpperCase(text.charAt(0)) +
               text.substring(1).toLowerCase();
    }

    public static int countOccurrences(String text, char target) {
        int count = 0;
        for (char c : text.toCharArray()) {
            if (c == target) count++;
        }
        return count;
    }
}

// Called on the class — no StringUtils object created:
System.out.println(StringUtils.isPalindrome("racecar"));  // true
System.out.println(StringUtils.capitalise("hELLO"));      // Hello
System.out.println(StringUtils.countOccurrences("banana", 'a'));  // 3

// ── Static factory methods — named constructors: ───────────────────────
public class Temperature {
    private final double celsius;

    // Private constructor — only created through factory methods:
    private Temperature(double celsius) {
        this.celsius = celsius;
    }

    // Named static factory methods — intent is crystal clear:
    public static Temperature ofCelsius(double c) {
        return new Temperature(c);
    }

    public static Temperature ofFahrenheit(double f) {
        return new Temperature((f - 32) * 5 / 9);
    }

    public static Temperature ofKelvin(double k) {
        if (k < 0) throw new IllegalArgumentException(
            "Kelvin cannot be negative: " + k);
        return new Temperature(k - 273.15);
    }

    public double toCelsius()    { return celsius; }
    public double toFahrenheit() { return celsius * 9 / 5 + 32; }
    public double toKelvin()     { return celsius + 273.15; }
}

Temperature boiling = Temperature.ofCelsius(100.0);
Temperature body    = Temperature.ofFahrenheit(98.6);
Temperature abs     = Temperature.ofKelvin(0);

System.out.printf("Boiling: %.1f°F%n",    boiling.toFahrenheit()); // 212.0°F
System.out.printf("Body:    %.1f°C%n",    body.toCelsius());       // 37.0°C
System.out.printf("Abs zero: %.1f°C%n",   abs.toCelsius());        // -273.2°C

// ── What static methods CANNOT do: ───────────────────────────────────
public class Counter {
    private int count = 0;     // instance field

    public static void badMethod() {
        // count++;             // COMPILE ERROR — cannot access instance field
        // getCount();          // COMPILE ERROR — cannot call instance method
        // System.out.println(this); // COMPILE ERROR — no this in static context
    }

    public static Counter create() {
        return new Counter();   // ✓ can create instances
    }
}

static Blocks — Class Initialisation

A static initialisation block is a block of code preceded by the static keyword that runs once when the class is first loaded into the JVM. It runs before any object is created and before any static method is called. Static blocks are used when static field initialisation requires logic that cannot be expressed in a single initialiser expression — multi-step setup, exception handling, reading a configuration file, populating a static map, or initialising a connection pool. A class can have multiple static blocks; they run in the order they appear in the source file, top to bottom. Static variable declarations and static blocks are interleaved in execution order — a static variable declared after a static block cannot be referenced by that block, but one declared before it can. The static block is also the place to handle checked exceptions that cannot be thrown from a field initialiser. Field initialisers cannot throw checked exceptions directly, but a static block can catch them. The common pattern is to catch a checked exception in a static block and either rethrow it wrapped in an unchecked exception or log it and use a fallback value.
Java
// ── Static block — runs once when class is first loaded: ─────────────
public class CountryCodeMap {
    // Static field — populated by static block:
    private static final Map<String, String> CODE_MAP;
    private static final Map<String, String> CURRENCY_MAP;

    static {
        // This runs ONCE when CountryCodeMap class is first used.
        System.out.println("Loading country data...");

        Map<String, String> codes = new HashMap<>();
        codes.put("US", "United States");
        codes.put("GB", "United Kingdom");
        codes.put("DE", "Germany");
        codes.put("JP", "Japan");
        codes.put("AU", "Australia");
        CODE_MAP = Collections.unmodifiableMap(codes);

        Map<String, String> currencies = new HashMap<>();
        currencies.put("US", "USD");
        currencies.put("GB", "GBP");
        currencies.put("DE", "EUR");
        currencies.put("JP", "JPY");
        currencies.put("AU", "AUD");
        CURRENCY_MAP = Collections.unmodifiableMap(currencies);

        System.out.println("Loaded " + CODE_MAP.size() + " countries.");
    }

    public static String getCountryName(String code) {
        return CODE_MAP.getOrDefault(code, "Unknown");
    }

    public static String getCurrency(String code) {
        return CURRENCY_MAP.getOrDefault(code, "Unknown");
    }
}

System.out.println(CountryCodeMap.getCountryName("GB"));  // United Kingdom
System.out.println(CountryCodeMap.getCurrency("JP"));     // JPY

// ── Static block for exception handling: ─────────────────────────────
public class DatabaseDriver {
    private static final Driver driver;

    static {
        // Field initialiser cannot throw checked exception.
        // Static block can catch and handle it:
        try {
            driver = DriverManager.getDriver("jdbc:postgresql://localhost/db");
        } catch (SQLException e) {
            throw new ExceptionInInitializerError(
                "Failed to load database driver: " + e.getMessage());
        }
    }
}

// ── Order of execution — static block and field interleave: ──────────
public class InitOrder {
    static int a = 10;                      // 1st: a = 10

    static {
        System.out.println("Block 1: a=" + a);  // 2nd: prints 10
        b = 20;                             // can assign b (declared later)
        // System.out.println(b);           // COMPILE ERROR — forward reference
    }

    static int b = 30;                      // 3rd: b = 30 (overrides the 20)

    static {
        System.out.println("Block 2: b=" + b);  // 4th: prints 30
    }
}
// Output when class is loaded:
// Block 1: a=10
// Block 2: b=30

static Nested Classes

Java allows classes to be defined inside other classes. A static nested class is a class defined inside another class with the static keyword. It is logically related to its enclosing class — they share the same source file, package access, and name — but it has no reference to any instance of the enclosing class. It behaves like a top-level class that happens to live in another class's namespace. This is in contrast to an inner class (non-static nested class), which does hold an implicit reference to the enclosing object and can access its instance members. The difference matters: a static nested class can be instantiated without any enclosing object, while an inner class always requires an enclosing instance first. Static nested classes are the correct design choice when the nested class is closely related to the outer class but does not need access to the outer class's instance state. The Builder pattern almost universally uses static nested classes precisely because a Builder object is created independently (new Config.Builder()) without needing a pre-existing Config instance to attach to.
Java
// ── Static nested class — no enclosing instance required: ───────────
public class Config {
    private final String host;
    private final int    port;
    private final boolean ssl;

    private Config(Builder builder) {
        this.host = builder.host;
        this.port = builder.port;
        this.ssl  = builder.ssl;
    }

    public String  getHost() { return host; }
    public int     getPort() { return port; }
    public boolean isSsl()   { return ssl;  }

    // ── Static nested Builder class: ──────────────────────────────────
    public static class Builder {       // static — no Config instance needed
        private String  host;
        private int     port = 8080;
        private boolean ssl  = false;

        public Builder host(String host) {
            this.host = host;
            return this;
        }
        public Builder port(int port) {
            this.port = port;
            return this;
        }
        public Builder ssl(boolean ssl) {
            this.ssl = ssl;
            return this;
        }
        public Config build() {
            if (host == null || host.isBlank())
                throw new IllegalStateException("host is required");
            return new Config(this);
        }
    }
}

// ── Static nested class instantiated without enclosing instance: ──────
Config.Builder builder = new Config.Builder();   // no Config object needed
Config config = builder.host("api.example.com").port(443).ssl(true).build();

// ── vs inner class — requires enclosing instance: ─────────────────────
public class Outer {
    private int outerValue = 42;

    public class Inner {            // non-static inner class
        public void print() {
            System.out.println(outerValue);  // can access outer field
        }
    }
}

Outer outer = new Outer();
Outer.Inner inner = outer.new Inner();   // requires Outer instance
inner.print();   // 42

// Static nested: new Outer.StaticNested() — no outer instance needed
// Inner class:   outer.new Inner()        — outer instance required

Related Topics in Object-Oriented Programming

OOP Concepts
Object-Oriented Programming (OOP) is a programming paradigm that organises software around objects — self-contained units that combine data (fields) and behaviour (methods). Java is a class-based, object-oriented language where almost everything is an object. OOP provides four foundational principles: encapsulation, inheritance, polymorphism, and abstraction. Together they produce software that is modular, reusable, maintainable, and easier to reason about as systems grow in complexity.
Classes
A class is the fundamental building block of Java. It is a blueprint that defines the structure and behaviour of objects — what data each object holds (fields) and what operations it can perform (methods). Every Java program is composed of classes. Understanding how to design a class well — choosing the right access modifiers, separating state from behaviour, and writing cohesive single-responsibility classes — is the foundation of object-oriented programming in Java. This entry covers class anatomy, fields, methods, access modifiers, static vs instance members, the this keyword, and class design principles.
Objects
An object is a runtime instance of a class. Where a class is a blueprint that exists in source code, an object is a living entity that exists in memory during program execution — it has its own identity, its own state stored in its fields, and the ability to respond to method calls. Every object has three fundamental properties: identity (a unique memory address), state (the current values of its fields), and behaviour (the methods it responds to). This entry covers object identity vs equality, the Object class hierarchy, object state and mutation, method calls, toString, equals and hashCode, and the object lifecycle.
Object Creation
Object creation in Java is the process of allocating memory, initialising fields, and running constructor logic to bring an object into existence. The new keyword is the primary mechanism, but Java also provides factory methods, builder patterns, copy constructors, and object cloning. Constructors are special methods that set up the initial state — their design determines how easy or difficult the class is to use correctly. This entry covers constructors in depth, constructor overloading and chaining, copy constructors, factory methods, the builder pattern, and the difference between shallow and deep copy.