Constants & final

final locks a variable to a single assignment — it does not, by itself, make the object it refers to immutable, a distinction Java requires you to keep straight.

final is Java's keyword for expressing 'this variable is assigned exactly once.' Applied to local variables, parameters, and fields, it prevents reassignment after initialization. Combined with static, it is the standard way to declare a true constant. This topic covers final in all its variable contexts, static final constants, compile-time constant expressions, and the crucial distinction between a final reference and a genuinely immutable object.

final Variables — the Core Rule

A variable declared final can be assigned a value exactly once. After that single assignment, any further assignment is a compile-time error. This applies uniformly to local variables, parameters, instance fields, and static fields.

final int max = 100;
// max = 200; // compile error: cannot assign a value to final variable max

Blank Final Fields

A blank final field is a final field declared without an initializer at the point of declaration. It is legal, provided the field is definitely assigned exactly once by the end of every constructor (for an instance field) or in a static initializer block (for a static field).

If a class has multiple constructors, every one of them must assign the blank final field exactly once (directly, or by delegating via this(...) to a constructor that does) — the compiler verifies this for every possible construction path.

public class Account {
    private final String ownerName; // blank final field

    public Account(String ownerName) {
        this.ownerName = ownerName; // must be assigned here (or in every constructor)
    }
}

final Parameters

A method or constructor parameter declared final cannot be reassigned within the method body. This is purely a safeguard against accidentally overwriting a parameter's value partway through a method — it has no effect on the caller and no effect on the object the parameter refers to.

void process(final int quantity) {
    // quantity = quantity + 1; // compile error: quantity is final
    System.out.println(quantity);
}

final References Versus Immutable Objects

This is one of the most important distinctions in Java: final on a reference variable only prevents that variable from being reassigned to point at a different object. It says nothing about whether the object it points to can be mutated. A final reference to a mutable object can still have that object's internal state changed freely through its own methods.

The final keyword only locks the variable names to always point at the same ArrayList object; the ArrayList itself is fully mutable, and its contents change freely.

Never say 'final makes an object immutable' — final constrains the variable, not the referenced object. Genuine immutability (see the Immutability topic) requires the class itself to be designed with no mutator methods, final fields, and defensive copying.

final java.util.List<String> names = new java.util.ArrayList<>();
names.add("Alice"); // fine: mutating the object's contents, not reassigning the reference
names.add("Bob");     // also fine
// names = new java.util.ArrayList<>(); // compile error: cannot reassign final variable names

static final Constants

The idiomatic way to declare a true, class-wide constant in Java is static final: static means one shared copy per class, final means it can never be reassigned after initialization. By convention, constants are named in SCREAMING_SNAKE_CASE.

public class Physics {
    public static final double SPEED_OF_LIGHT = 299_792_458.0; // meters per second
    public static final int MAX_RETRIES = 3;
}

// usage elsewhere:
double c = Physics.SPEED_OF_LIGHT;

Compile-Time Constants and Constant Expressions

A compile-time constant is a final variable of a primitive type or String, initialized with a constant expression — one the compiler can fully evaluate at compile time (literals, and combinations of literals using only operators, with no method calls or non-final variable references). Compile-time constants are inlined directly into the bytecode of any code that reads them.

A final variable initialized with a non-constant expression (e.g. a method call, or another non-final variable) is still a final variable — it just isn't a compile-time constant, and won't be inlined the same way. This distinction mostly matters for binary compatibility (recompiling a dependent class after changing a constant's value) and for switch statements on constants.

static final int SECONDS_PER_DAY = 60 * 60 * 24; // compile-time constant: 86400
static final String GREETING = "Hello, " + "World!"; // compile-time constant String concatenation

final in switch Case Labels

A traditional switch statement's case labels on int/char/String require compile-time constant expressions — this is one concrete place where the compile-time-constant distinction (versus an ordinary final variable) actually matters.

static final int MONDAY = 1; // compile-time constant: OK as a case label

switch (day) {
    case MONDAY: // valid
        break;
}

Naming Conventions for Constants

By strong convention, static final fields intended as constants use SCREAMING_SNAKE_CASE (all uppercase, words separated by underscores): MAX_SIZE, DEFAULT_TIMEOUT_MS, PI. This visually distinguishes them from ordinary fields and local variables (lowerCamelCase) at a glance.

Initialization Rules Summary

final local variable: must be definitely assigned exactly once before use (initializer, or exactly one assignment reachable before any read). • final instance field: must be assigned exactly once by the end of every constructor (or at declaration). • final static field: must be assigned exactly once at declaration or in a static initializer block. • final parameter: implicitly assigned once by the call; cannot be reassigned in the body.

Key Takeaways

final means 'assigned exactly once' for the variable itself — it never means the referenced object becomes immutable.

A blank final field has no initializer at declaration but must be assigned exactly once by the end of every constructor.

static final is the idiomatic pattern for a true class-wide constant; name it SCREAMING_SNAKE_CASE.

A compile-time constant is a final primitive/String initialized with a constant expression the compiler can evaluate and inline; this specifically matters for switch case labels and binary compatibility.

Genuine object immutability requires deliberate class design (final fields, no mutators, defensive copying) — final on a variable alone does not provide it.

final List<String> list = new ArrayList<>(); // assuming 'final' means list's contents can never change final List<String> list = new ArrayList<>(); list.add("item"); // this is fine; the object is still mutable // only 'list = someOtherList;' would be a compile error final only prevents reassigning the variable list to a different object; it does not prevent mutating the ArrayList object list currently points to.

final int total; total = 1; total = 2; // second assignment final int total; total = 1; // exactly one assignment, on a reachable path

A final variable can be assigned exactly once; a second assignment on any reachable path is a compile-time error, even if the first assignment is later 'overwritten' logically.

public class Config { private final String name; // blank final never assigned in some constructor public Config() { } public Config(String name) { this.name = name; } } public class Config { private final String name; public Config() { this("default"); } public Config(String name) { this.name = name; } }

Every constructor path must assign a blank final field exactly once; the no-argument constructor above never assigns name, which is a compile error until it delegates (or assigns directly).

What exactly does declaring a variable final guarantee, and what does it NOT guarantee?

It guarantees the variable can be assigned a value exactly once and never reassigned afterward. It does NOT guarantee that the object the variable refers to (if it's a reference type) is immutable — the object's internal state can still change via its own methods.

What is a blank final field, and what must be true about every constructor of its class?

A final field declared without an initializer at the point of declaration. Every constructor of the class must assign it exactly once (directly or by delegating to a constructor that does), on every possible execution path.

What is the idiomatic pattern for declaring a true class-wide constant in Java?

static final, combined with a primitive or String type and SCREAMING_SNAKE_CASE naming, e.g. public static final int MAX_RETRIES = 3;

What makes a final variable a 'compile-time constant' specifically, versus just an ordinary final variable?

It must be a final variable of primitive type or String, initialized with a constant expression the compiler can fully evaluate at compile time (only literals and operators on other constant expressions, no method calls or non-final variable reads).

Where does the compile-time-constant distinction concretely matter in the language?

Traditional switch statement case labels on int/char/String require compile-time constant expressions; an ordinary (non-compile-time-constant) final variable cannot be used as a case label.

Can a final parameter's value be changed by the method that receives it?

No — a final parameter cannot be reassigned inside the method body. This affects only the local parameter variable within that method; it has no effect on the caller's original argument.

Why is 'final makes an object immutable' a technically wrong statement?

Because final constrains the variable (the reference itself, forbidding reassignment), not the object being referenced. A final reference to a mutable object (e.g. an ArrayList) still allows full mutation of that object's contents through its own methods.

Write a class ImmutablePoint with final int fields x and y, set only via the constructor, demonstrating a case where final does contribute to real immutability (because int has no internal state to mutate).

public final class ImmutablePoint { private final int x; private final int y; public ImmutablePoint(int x, int y) { this.x = x; this.y = y; } public int getX() { return x; } public int getY() { return y; } }

Because x and y are primitives (int), final on them is sufficient for true immutability — there's no internal mutable state to worry about, unlike a final reference to a mutable object like a List.

Does the case MAX: label compile? Why?

Yes — MAX is a compile-time constant (a final int initialized with the constant expression 10 * 5, evaluable entirely at compile time), which is exactly what a traditional switch case label on an int requires.

What does this program print, and what would happen if the commented-out last line were uncommented?

It prints 'Hello, World!' — appending mutates the StringBuilder object in place, which final permits since the variable sb itself isn't reassigned. Uncommenting the last line would cause a compile error, because it attempts to reassign the final variable sb to a different object.

public class Settings {
    public static final int MAX = 10 * 5;
    // ...
    switch (level) {
        case MAX:
            System.out.println("max level");
            break;
    }
}
final StringBuilder sb = new StringBuilder("Hello");
sb.append(", World!");
System.out.println(sb);
// sb = new StringBuilder(); // this line, if uncommented