Variables
Java variables come in four flavors — local, parameters, instance, and static — each with its own scope, lifetime, and initialization rules.
A variable is a named storage location holding a value of a declared type. Java distinguishes four kinds of variables by where they're declared and how long they live: local variables (inside a method/block), parameters (a method's inputs), instance variables (per-object state), and static/class variables (shared across all instances of a class). This topic covers declaration, initialization, scope, and Java's definite assignment rule.
The Four Kinds of Variables
public class Account {
private static int accountsCreated; // static (class) variable
private double balance; // instance variable
public Account(double openingBalance) { // openingBalance is a parameter
double fee = 0.0; // fee is a local variable
this.balance = openingBalance - fee;
accountsCreated++;
}
}Local Variables
Local variables are declared inside a method, constructor, or block. They exist only for the duration of that block's execution and are not accessible outside it. Java requires local variables to be definitely assigned a value before they are read.
void printSum() {
int a = 3;
int b = 4;
int sum = a + b; // local to printSum
System.out.println(sum);
} // a, b, sum all go out of scope hereParameters
Parameters are variables declared in a method or constructor's parameter list, receiving the caller's arguments. Like local variables, they exist only for the duration of the call, but unlike other local variables, they are always considered definitely assigned — the caller's argument value initializes them automatically.
int add(int a, int b) { // a and b are parameters
return a + b;
}Instance Variables
Instance variables (also called fields) are declared inside a class but outside any method — each object created from the class gets its own independent copy. They exist for as long as the object they belong to is reachable.
public class Account {
private double balance; // instance variable, unique per Account object
}
Account a1 = new Account();
Account a2 = new Account();
// a1.balance and a2.balance are independent storage locationsStatic (Class) Variables
A static variable belongs to the class itself, not to any individual instance — there is exactly one copy, shared by all objects of that class (and accessible even with no instances created). Declared with the static modifier.
public class Account {
private static int accountsCreated = 0; // shared by all Account instances
public Account() {
accountsCreated++;
}
}
new Account();
new Account();
System.out.println(Account.accountsCreated); // prints 2Declaration, Initialization, and Assignment
Declaration introduces a variable's name and type without necessarily giving it a value. Initialization supplies a value at the point of declaration. Assignment sets (or later changes) a variable's value using =.
int count; // declaration only, no value yet
count = 0; // assignment
int total = 100; // declaration combined with initializationScope
Scope is the region of source code in which a variable's name is visible and can be referenced. Local variables and parameters are scoped to the block/method in which they are declared (and any nested blocks within it); instance variables are scoped to the whole class (all non-static methods can see them); static variables are scoped to the whole class as well, accessible from both static and instance contexts.
void example() {
int outer = 1;
if (true) {
int inner = 2;
System.out.println(outer); // OK: outer's scope includes this nested block
}
// System.out.println(inner); // compile error: inner is out of scope here
}Lifetime (Conceptual)
Lifetime is when a variable's storage actually exists during execution, as distinct from scope (where its name is visible in source). A local variable's storage typically exists only while its enclosing block is executing (conceptually, on the call stack); an instance variable's storage exists as long as its object remains reachable; a static variable's storage exists for the lifetime of its class being loaded in the JVM. Exact storage mechanics (stack vs heap layout) are JVM-implementation details, covered in the Memory Model Overview topic.
Definite Assignment
Java's compiler enforces definite assignment for local variables: a local variable must be assigned a value along every possible execution path before it is read, or the code fails to compile. This does not apply to fields — instance and static fields are automatically initialized to a default value (0, false, or null, depending on type) if not explicitly initialized.
The compiler cannot prove x is assigned on every path (the else branch is missing), so it refuses to compile the read of x.
void example(boolean flag) {
int x;
if (flag) {
x = 1;
}
System.out.println(x); // compile error: x might not have been assigned
}class Counter {
int value; // instance field: automatically defaults to 0, no definite-assignment error
}final Variables
Any of the four kinds of variables can be declared final, meaning it can be assigned exactly once and never reassigned afterward. A final local variable must still satisfy definite assignment before use, just like a non-final one. final is covered in full detail in the Constants & final topic.
final int max = 100;
// max = 200; // compile error: cannot assign a value to final variable maxVariable Shadowing
Shadowing occurs when a variable declared in an inner scope has the same name as one in an outer scope, temporarily hiding the outer one within the inner scope. The most common legitimate case is a constructor/method parameter sharing a name with an instance field, resolved using this.fieldName.
Unlike some languages, Java does not allow a local variable to shadow another local variable (or parameter) already in scope in the same or an enclosing block within the same method — that is a compile error, not silent shadowing.
public class Box {
private int size;
public Box(int size) { // parameter 'size' shadows the field 'size'
this.size = size; // this.size (field) = size (parameter)
}
}Key Takeaways
Java has four variable kinds: local variables, parameters, instance variables, and static variables — distinguished by where they're declared and how long they live.
Local variables must be definitely assigned before use (compiler-enforced); fields (instance/static) get automatic default values if not explicitly initialized.
Scope is where a name is visible in source; lifetime is when its storage actually exists during execution — related but distinct concepts.
Any variable can be declared final to forbid reassignment after its single initial assignment.
A parameter or local variable may legitimately shadow an instance field of the same name; this.fieldName disambiguates inside the shadowing scope.
int x; System.out.println(x); // local variable read before assignment int x = 0; System.out.println(x);
Unlike fields, local variables have no default value and must be definitely assigned along every path before being read, or the code fails to compile.
public Box(int size) { size = size; } // assigns parameter to itself public Box(int size) { this.size = size; }
Without this., 'size = size' just reassigns the parameter to itself and never touches the instance field — a classic shadowing mistake that silently leaves the field at its default value.
int total; int total; // attempting to redeclare in the same scope int total; // declare once; reuse the same variable afterward
Declaring two local variables with the same name in the same (or a nested) scope is a compile error in Java — it does not silently shadow like in some other languages.
Name the four kinds of Java variables and how each is distinguished from the others.
Local variables (declared inside a method/block), parameters (a method's declared inputs), instance variables (per-object fields), and static/class variables (one shared copy per class, marked static).
What is definite assignment, and which kinds of variables does it apply to?
The compiler-enforced rule that a local variable must be assigned a value along every possible execution path before it is read. It applies to local variables (and parameters, which are always definitely assigned by the call); it does not apply to fields, which get automatic default values.
What default value does an uninitialized instance int field get? What about an uninitialized local int variable?
An instance int field defaults to 0 automatically. A local int variable has no default — it must be explicitly assigned before it can be read, or the compiler rejects the code.
Explain the difference between scope and lifetime with an example.
Scope is where in the source code a variable's name is visible (e.g. within a method body); lifetime is when the variable's storage actually exists during program execution. A local variable's name is scoped to its block, and its storage typically exists only while that block executes — the two usually align for locals but are conceptually distinct ideas.
What does declaring a variable final change about how it can be used?
It restricts the variable to being assigned exactly once — any attempt to reassign it afterward is a compile-time error. It does not change definite-assignment requirements; a final local variable still must be assigned before use.
What is variable shadowing, and give the common legitimate use case for it.
Shadowing is when an inner-scope variable has the same name as an outer-scope one, hiding the outer one within the inner scope. The common legitimate case is a constructor/setter parameter sharing a name with the instance field it sets, disambiguated with this.fieldName.
Can a static variable be accessed from an instance (non-static) method? Can an instance variable be accessed from a static method directly?
A static variable can be accessed from both static and instance methods. An instance variable cannot be accessed directly from a static method, because a static method has no implicit 'current object' (this) to belong to — it would need an explicit object reference.
Does this compile? Why, in contrast to a version missing the else branch?
Yes, it compiles. Because both the if and else branches assign result, the compiler can prove it is definitely assigned along every possible path before it's read — unlike a version with only the if branch, which leaves a path where result is unassigned.
Write a class Rectangle with instance fields width and height, a constructor using shadowed parameter names resolved with this., and a static field count that increments per instance created.
public class Rectangle { private static int count = 0; private double width; private double height; public Rectangle(double width, double height) { this.width = width; this.height = height; count++; } public static int getCount() { return count; } } this.width and this.height disambiguate the shadowed parameters from the instance fields; count is static, so it is shared across all Rectangle instances and incremented once per construction.
Does show() compile, and why can it access shared?
Yes — a static variable is accessible from instance (non-static) methods of the same class without qualification, since there is exactly one shared copy regardless of which instance is calling.
void demo(boolean condition) {
int result;
if (condition) {
result = 1;
} else {
result = 2;
}
System.out.println(result);
}public class Demo {
static int shared = 10;
void show() {
System.out.println(shared); // accessing static from instance method
}
}