Variables and Assignment
Declare, initialise and reassign variables, and know the rules on scope, default values and definite assignment that the compiler enforces.
Type name = initialValue;
Type name; // declare now, assign before first read
name = newValue; // reassignA 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 timeThe 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
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.
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 falseScope 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.
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=14Copying 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.
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