Variables and Assignment

Declare, initialise and reassign variables, and know the rules on scope, default values and definite assignment that the compiler enforces.

SyntaxType name = initialValue; Type name; // declare now, assign before first read name = newValue; // reassign

A variable is a named slot with a fixed type. Java checks that type at compile time, so a variable declared as int can never hold a String. You declare it with the type, the name and usually a first value:

int retries = 3;
String status = "PENDING";
retries = retries - 1;   // reassign: no type this time

The type appears once, at the declaration. Every later = is an assignment that replaces the value. Names are camelCase by convention (orderTotal, maxRetries), and they should say what the value means, not what type it is.

Locals must be assigned before use

Fields of a class get a default value: 0 for numbers, false for boolean, null for references. Local variables do not. The compiler tracks definite assignment and refuses to compile any read of a local that might not have a value yet. This is why you can declare long total; and fill it in both branches of an if, but not in only one of them.

The default on fields is a common source of quiet bugs in services: an int field that was never set reads as 0, which looks like real data. If "not set" is a meaningful state, use a wrapper type (Integer, which can be null) or make the field required in the constructor.

Scope

A local lives from its declaration to the closing brace of the block it is in. A variable declared inside a loop body or an if block disappears when that block ends, and Java does not allow a local to shadow another local of the same name in an enclosing block. Declare variables in the smallest scope that works and as close to their first use as you can; it keeps methods readable and stops a value from leaking into code that should not see it.

Primitives copy values, references copy the pointer

Assigning one int to another copies the number. Assigning one List variable to another copies only the reference, so both names point at the same list and a change through one is visible through the other. This matters as soon as you pass a request object or a cached list around a service: "copying" it with = shares it. Make a real copy with new ArrayList<>(list) or List.copyOf(list) when you need one.

Assignment is also an expression whose value is the assigned value, so a = b = 10 sets both. Use that sparingly; a line that both assigns and tests is harder to review.

Example

Try it yourself
Loading editor…
Press Run to execute the code.

When to use it

  • Count retries for a failing call to a downstream payment API and stop once the counter reaches zero.
  • Declare a response status before an if/else so the compiler proves every branch sets it.
  • Take a defensive copy of a list passed into a service so callers cannot change its internal state.

More examples

Fields get defaults, locals do not

Fields are filled with zero, false or null when the object is created, so an unset field looks like real data. A local has no default, and the commented-out read would not compile.

Example · java
class RequestStats {
    int count;
    long bytes;
    String lastPath;
    boolean healthy;
}

void main() {
    RequestStats stats = new RequestStats();
    IO.println(stats.count + " " + stats.bytes + " " + stats.lastPath + " " + stats.healthy);

    // int pending;
    // IO.println(pending); // compile error: variable pending might not have been initialized
}
// Output:
// 0 0 null false

Scope and swapping two values

Assigning primary to replica directly would lose the first value, so a temporary holds it. The loop variable and squared are gone once the loop ends, which keeps them from being misused later in the method.

Example · java
void main() {
    String primary = "db-eu-1";
    String replica = "db-eu-2";
    String tmp = primary;   // a swap needs a third variable
    primary = replica;
    replica = tmp;
    IO.println("primary=" + primary + ", replica=" + replica);

    int total = 0;
    for (int i = 1; i <= 3; i++) {
        int squared = i * i;   // exists only inside the loop body
        total += squared;
    }
    // IO.println(squared); // compile error: cannot find symbol
    IO.println("total=" + total);
}
// Output:
// primary=db-eu-2, replica=db-eu-1
// total=14

Copying a primitive versus copying a reference

Changing copy leaves limit alone, but adding through alias changes the one list both names point at. List.copyOf takes a snapshot, so later changes to tags do not reach it.

Example · java
void main() {
    int limit = 10;
    int copy = limit;
    copy = 20;
    IO.println("limit=" + limit + " copy=" + copy);

    List<String> tags = new ArrayList<>(List.of("api"));
    List<String> alias = tags;               // same list, second name
    alias.add("internal");
    List<String> snapshot = List.copyOf(tags); // a real, unmodifiable copy
    tags.add("beta");
    IO.println("tags=" + tags);
    IO.println("snapshot=" + snapshot);
}
// Output:
// limit=10 copy=20
// tags=[api, internal, beta]
// snapshot=[api, internal]

Discussion

  • Be the first to comment on this lesson.