Declaring Local and Instance Variables

Declaring Local and Instance Variables

OCP Exam Topic — Understand scoping, initialization rules, default values, effectively final variables, and the use of var in method bodies. Covered in Chapter 5 of OCP Java SE 21 Developer Study Guide (Selikoff & Boyarsky).


Local Variables

A local variable is declared inside a method, constructor, or initializer block. It lives only while that block is executing and is destroyed when the block exits.

The critical rule: local variables must be explicitly initialized before they are read. The compiler tracks every possible execution path and reports an error if a variable might be read before it has been assigned.

java
void example() { int x; // declared but not initialized System.out.println(x); // compile error: variable x might not have been initialized } void fixed() { int x = 0; // initialized System.out.println(x); // OK }

The compiler analyzes branches, not just linear flow:

java
void branch(boolean flag) { int x; if (flag) x = 10; // compile error: x is only initialized on one branch System.out.println(x); } void branchFixed(boolean flag) { int x; if (flag) x = 10; else x = 20; // both branches initialize x System.out.println(x); // OK }

Instance Variables

An instance variable (also called a field) is declared inside a class but outside any method. It exists for the lifetime of the object that contains it.

Instance variables are automatically assigned a default value if not explicitly initialized:

Type Default Value
byte, short, int, long 0
float, double 0.0
boolean false
char '' (null character)
Object reference (any class) null
java
class Counter { int count; // defaults to 0 String label; // defaults to null boolean active; // defaults to false void show() { // No compile error — instance variables always have a value System.out.println(count + " " + label + " " + active); } }

Because default values are guaranteed, the compiler does not require instance variables to be explicitly initialized before use.


Variable Scope

Scope defines where a variable is accessible. Java uses block scope: a variable is accessible from the point of declaration to the closing brace of the block in which it was declared.

java
void scopeDemo() { int outer = 1; { int inner = 2; System.out.println(outer); // OK System.out.println(inner); // OK } System.out.println(outer); // OK System.out.println(inner); // compile error: inner is out of scope }

Loop variables are scoped to the loop body:

java
for (int i = 0; i < 5; i++) { System.out.println(i); // OK inside loop } // System.out.println(i); // compile error: i out of scope

Effectively Final Variables

A variable is effectively final if it is never reassigned after its initial assignment — even if it was not declared with the final keyword. The compiler treats it the same as a final variable.

This matters in particular for lambda expressions and local classes, both of which can only capture local variables that are effectively final:

java
void process(List<String> names) { String prefix = "Hello, "; // effectively final — never reassigned names.forEach(name -> System.out.println(prefix + name)); // OK String greeting = "Hi"; greeting = "Hey"; // reassigned — no longer effectively final // names.forEach(name -> System.out.println(greeting + name)); // compile error }

Declaring a variable final explicitly is still good practice because it makes the intent clear and catches accidental reassignments at compile time.


Using var in Method Bodies

Java 10 introduced local variable type inference with var. When you use var, the compiler infers the type from the initializer expression.

var is only permitted in:

  • Local variable declarations with an initializer
  • for loop index variables
  • Enhanced for loop variables
  • try-with-resources variables

var is not permitted for:

  • Instance or class (static) fields
  • Method parameters
  • Return types
  • var without an initializer
java
void varExamples() { var count = 42; // inferred as int var name = "Alice"; // inferred as String var list = new ArrayList<String>(); // inferred as ArrayList<String> for (var item : list) { // inferred as String System.out.println(item); } } // Compile errors involving var: // var field = 10; // NOT allowed as instance variable // void method(var x) { } // NOT allowed as parameter // var noInit; // NOT allowed without initializer

Once inferred, the type is fixed — var does not make Java dynamically typed:

java
var x = 5; x = "hello"; // compile error: int cannot receive a String

Key Points to Remember

  • Local variables have no default value and must be initialized before use; the compiler enforces this on every code path.
  • Instance variables always have a default value (0, false, null, etc.) and do not require explicit initialization.
  • Scope is determined by the enclosing block; variables are not accessible outside it.
  • A variable is effectively final if it is assigned exactly once; lambdas and local classes can only capture effectively-final local variables.
  • var infers the type from the initializer and is legal only for local variables, for loop variables, and try-with-resources; it is not allowed for fields, parameters, or return types.