A machine that remembers, a button press that forgets#
Picture a vending machine in a hallway. Its display shows your credit: drop in a quarter, go and find another, and the 25 cents is still there when you come back. Press a button and, on a good day, the machine works out your change on a scratch pad inside, drops the coins, and throws the scratch pad away.
Java variables come in those two kinds. Ask of each one: where in the code can I use its name? That region is its scope. When, while the program runs, does it exist? That stretch of time is its lifetime. Then ask where in memory it sits. One class, VendingMachine, grows through this whole guide, and every step shows the real output.
Scope: the curly braces decide#
You already declare a variable as a type, then a name: int credit = 100;. The one new idea is that a declaration has a reach. A pair of curly braces, { and }, marks a block. A variable declared inside a block can be used from its declaration down to that block’s closing curly brace, and nowhere else. This is the block rule.
The machine’s first version lives in main. It sells a 75-cent can and works out the change inside an if block.
The last line prints change, which was declared inside the if block. Will it compile? If not, which line will the compiler point at?
Show the answer
No. change stops existing at the closing curly brace of the if. The compiler points at line 9, the line that uses the name.
public class VendingMachine {
public static void main(String[] args) {
int credit = 100; // cents inserted so far
int price = 75; // one can
if (credit >= price) {
int change = credit - price; // change is born here...
System.out.println("Vend! Change: " + change);
} // ...and its scope ends here
System.out.println("Coins returned: " + change);
}
}
VendingMachine.java:9: error: cannot find symbol
System.out.println("Coins returned: " + change);
^
symbol: variable change
location: class VendingMachine
1 error
cannot find symbol means “I do not know this name here”, and the caret, ^, points at the use. The variable was real; it was just out of scope. The fix is to declare change in main’s own block, before the if, and only assign it inside:
public class VendingMachine {
public static void main(String[] args) {
int credit = 100;
int price = 75;
int change = 0; // declared in main's block
if (credit >= price) {
change = credit - price; // assigned, not declared again
System.out.println("Vend! Change: " + change);
}
System.out.println("Coins returned: " + change);
}
}
Vend! Change: 25
Coins returned: 25
Notice there is no int inside the if: a second declaration would fail with variable change is already defined in method main(String[]). And change starts at 0 because a variable needs a value before it is read, and the if might be skipped (int change; alone fails with variable change might not have been initialized). The usual convention is to declare each variable in the smallest block that needs it, so fewer lines can misuse it. Declaring everything at the top of the method also works.
For every use of a variable, find its declaration and follow it down to the closing curly brace of that block. A variable declared in a for loop’s parentheses belongs to the loop. So in for (int i = 0; i < 3; i++) { ... }, the name i is gone after the loop. Blocks nest: an inner block can use the outer block’s variables, never the other way round.
If you know Python, this is new: Python keeps a variable made inside an if for the rest of the function. Java ends it at the closing curly brace.
Parameters and locals belong to one method#
A real machine is an object, so VendingMachine becomes a class. The credit and the stock (cans inside) become instance variables, also called fields: declared in the class but outside every method, so each machine object gets its own. A parameter, like quarters in insertCoins(int quarters, int dimes), is filled in by each call and is in scope in its whole method body. A local variable, like count, is declared inside a method and follows the block rule.
insertCoins and restock each declare a local called count. main inserts 3 quarters and 1 dime, restocks 2 boxes of 6 cans, then inserts 2 dimes. On the last line, is count 2 or 6? Is credit 20 or 105?
Show the answer
count is 2 and credit is 105. Each call gets a fresh count; credit is a field, so it keeps its value between calls.
public class VendingMachine {
private int credit; // cents inserted, not yet spent
private int stock; // cans inside
public VendingMachine(int stock) {
this.stock = stock;
}
public void insertCoins(int quarters, int dimes) {
int count = quarters + dimes; // coins in this handful
credit = credit + quarters * 25 + dimes * 10;
System.out.println(count + " coins in, credit " + credit);
}
public void restock(int boxes) {
int count = boxes * 6; // cans added this time
stock = stock + count;
System.out.println(count + " cans added, stock " + stock);
}
public static void main(String[] args) {
VendingMachine lobby = new VendingMachine(4);
lobby.insertCoins(3, 1);
lobby.restock(2);
lobby.insertCoins(0, 2);
}
}
4 coins in, credit 85
12 cans added, stock 16
2 coins in, credit 105
Does this make sense? Two dimes are 2 coins, and 85 plus 20 cents is 105. The two counts never meet, so you can name a local for what it means in this method without checking the others. Parameters work the same way: restock using quarters would get cannot find symbol, and so would main.
Instance variables: in scope across the whole class#
The methods use credit and stock, yet nobody passed them in. A field’s scope is the whole class, wherever in the class it is written: a field declared at the very bottom is still usable in a method above it. In every constructor and every instance method (one without static), the plain name credit means this object’s credit.
The catch is a static method, such as main. It runs without any particular machine, so a plain credit inside it fails with non-static variable credit cannot be referenced from a static context. Notice the error is not cannot find symbol: the name is in scope, but Java asks which machine’s credit. Name one through a reference, as in lobby.getCredit(). The methods guide shows the same error for a method.
This pays off an old mystery: this.stock = stock; in the constructor. Two variables called stock are in scope there, the field and the parameter, and Java picks the nearest, the parameter. That is shadowing. this means “the object this method was called on”, so this.stock reaches the field. The class-writing guide shows what goes wrong without it.
Scope is where, lifetime is when#
Scope is about the text: which lines may mention a name. Lifetime is about time: from when the variable is created, while the program runs, until it is destroyed. You just saw both: count was born fresh on each call and gone after it, while credit lived on in the machine.
| Kind | Scope: where the name works | Lifetime: when it exists |
|---|---|---|
| Local variable | its declaration to the closing curly brace of its block | from when its declaration runs until its block ends |
| Parameter | the whole method body | from the start of the call until the method returns |
| Instance variable | the whole class; in a static method, only through an object, as in lobby.getCredit() | as long as the object that owns it |
static field | the whole class, static methods included | one copy for the whole class, while the class is loaded (in a small program, the whole run) |
The two can come apart. Soon buy will call makeChange. While makeChange runs, buy’s local change is out of scope, because makeChange cannot name it, yet it is still alive, waiting for buy to carry on. The call stack explains how.
The call stack: one frame per call#
Picture cafeteria trays: new ones go on top, and only the top one comes off. Each method call gets a stack frame, a block of memory holding that call’s parameters and local variables. A call pushes its frame onto the call stack. Returning pops it off, and its locals are gone. The caller’s frame waited underneath, untouched, and carries on.
Now buy calls makeChange to count out coins of one size. Each println is indented by call depth, so the output draws the stack sideways. The second purchase asks for coins worth 0 cents, on purpose, to make the deepest call fail.
While makeChange runs, how many frames are on the stack, and which one is on top?
Show the answer
Three: main at the bottom, then buy, then makeChange on top. The two below are paused, waiting.
public class VendingMachine {
private int credit;
private int stock;
public VendingMachine(int stock) {
this.stock = stock;
}
public void insertCoins(int quarters, int dimes) {
credit = credit + quarters * 25 + dimes * 10;
}
public void buy(int price, int coin) {
System.out.println(" buy(" + price + ", " + coin + ") starts");
int change = credit - price;
credit = 0;
stock--;
int coins = makeChange(change, coin);
System.out.println(" buy: drop " + coins + " coins, change was " + change);
System.out.println(" buy returns");
}
public int makeChange(int cents, int coin) {
System.out.println(" makeChange(" + cents + ", " + coin + ") starts");
int count = cents / coin;
System.out.println(" makeChange returns " + count);
return count;
}
public static void main(String[] args) {
System.out.println("main starts");
VendingMachine lobby = new VendingMachine(4);
lobby.insertCoins(5, 0);
lobby.buy(75, 25);
lobby.insertCoins(5, 0);
lobby.buy(75, 0); // a coin worth 0 cents: fails on purpose
System.out.println("main ends");
}
}
main starts
buy(75, 25) starts
makeChange(50, 25) starts
makeChange returns 2
buy: drop 2 coins, change was 50
buy returns
buy(75, 0) starts
makeChange(50, 0) starts
Exception in thread "main" java.lang.ArithmeticException: / by zero
at VendingMachine.makeChange(VendingMachine.java:25)
at VendingMachine.buy(VendingMachine.java:18)
at VendingMachine.main(VendingMachine.java:36)
In the first purchase, makeChange returns 2, its frame is popped, and buy resumes with change still holding 50. Just before that return, the stack looked like this, top first:
| Frame | What it holds |
|---|---|
makeChange (running) | cents = 50, coin = 25, count = 2, this |
buy (paused) | price = 75, coin = 25, change = 50, coins (not assigned yet), this |
main (paused) | args, lobby |
this is a hidden parameter every instance method gets: a reference to the object it was called on, which is how buy reaches credit. Notice the two coins. Each parameter is a copy of its argument, in its own frame, which is why a method cannot change its caller’s variable (see what a method cannot change).
The second purchase crashed: 50 / 0 is whole-number division by zero, which Java refuses with an ArithmeticException. Nothing caught it (the exceptions guide shows how), so Java printed a stack trace: the frames on the stack at that moment, top first, each with the line it had reached. makeChange is on top at line 25, buy waits at line 18, and main is at the bottom at line 36. main ends never prints: the uncaught exception popped every frame without letting it finish. The recursion guide piles up many frames of one method.
Every division: can the right side be 0? Whole-number division by 0 stops the program; floating-point division by 0 gives Infinity.
The method running right now is always the top frame, the one called most recently. main is at the bottom, because it was called first. “main is on top” is a favorite wrong answer.
Shrink it to the smallest case. If main calls nothing, the stack holds one frame. When main returns, the stack is empty and the program is over.
Objects live on the heap#
Objects often must outlast the call that made them. So every object, whether made with new, an array, or a String literal, lives on the heap: a large area of memory, separate from the stack. VendingMachine lobby = new VendingMachine(4); splits in two. The object goes on the heap. The variable lobby goes in main’s frame and holds a reference to the object:
STACK: main's frame HEAP
+-----------------------+ +-----------------------------+
| lobby o-------------+------->| a VendingMachine object |
+-----------------------+ | credit 0 |
| stock 4 |
+-----------------------------+
A reference is, in effect, the object’s address. Java never shows you the number; you can only follow it with a dot, as in lobby.insertCoins(4, 0). So a reference variable holds an address, never the object. Note that credit, an int, is on the heap, because it is part of the object. Where a variable is declared decides where it lives: locals and parameters in a frame, fields inside their object.
sameOne = lobby copies lobby, and gym gets its own new. Four quarters go into sameOne. What credit do lobby and gym show?
Show the answer
100 and 0. Copying the variable copied the address, so lobby and sameOne are one machine; gym is a second.
public class VendingMachine {
private int credit;
private int stock;
public VendingMachine(int stock) {
this.stock = stock;
}
public void insertCoins(int quarters, int dimes) {
credit = credit + quarters * 25 + dimes * 10;
}
public int getCredit() {
return credit;
}
public static void main(String[] args) {
VendingMachine lobby = new VendingMachine(4);
VendingMachine sameOne = lobby; // copies the reference
VendingMachine gym = new VendingMachine(4); // a second new: a second machine
sameOne.insertCoins(4, 0);
System.out.println("lobby credit: " + lobby.getCredit());
System.out.println("gym credit: " + gym.getCredit());
System.out.println(lobby == sameOne);
System.out.println(lobby == gym);
}
}
lobby credit: 100
gym credit: 0
true
false
On references, == asks “same object?”, which is why lobby == gym is false.
news?In any “what does this print?” question with objects, count the news. Each new makes exactly one object. b = a makes none: it copies a reference, so a change made through b shows up through a.
Strings are objects too, with one wrinkle worth meeting on purpose:
VendingMachine.javapublic class VendingMachine {
public static void main(String[] args) {
String shelf = "Cola";
String button = "Cola";
String built = new String("Cola");
System.out.println(shelf == button);
System.out.println(shelf == built);
System.out.println(shelf.equals(built));
}
}
true
false
true
Java shares one copy of each identical String literal (text in double quotes in the code), kept in what is called the string pool, so shelf == button is true. new always builds a separate object, so shelf == built is false, while equals compares the letters. Text from a Scanner is usually a new object, so compare text with equals, as the Strings guide explains.
Passing an object to a method: still a copy#
Java passes every argument by value: the parameter gets a copy of what the argument holds. For an object, that is a copy of the reference. The object is not copied, and it is not passed “by reference” either. So a method can follow its copy and change the object, but it cannot make the caller’s variable point somewhere else.
service sets the machine’s stock to 30, then points its parameter at a brand-new machine and sets that one’s stock to 99. Afterwards, is lobby’s stock 4, 30 or 99?
Show the answer
30. The first line followed the copied address to the lobby machine. The second moved only the method’s own copy.
public class VendingMachine {
private int credit;
private int stock;
public VendingMachine(int stock) {
this.stock = stock;
}
public int getStock() {
return stock;
}
public static void service(VendingMachine vm) {
vm.stock = 30; // follows the copy to the real machine
vm = new VendingMachine(0); // moves only the method's own copy
vm.stock = 99; // changes the new machine
}
public static void main(String[] args) {
VendingMachine lobby = new VendingMachine(4);
service(lobby);
System.out.println("lobby stock: " + lobby.getStock());
}
}
lobby stock: 30
lobby and vm were two variables in two frames holding one address. When service returned, the 99-can machine was left with no reference anywhere.
When a variable dies, and when an object can be collected#
A local or a parameter is gone, at the latest, when its method returns and its frame is popped. An instance variable lasts as long as its object. And an object stays on the heap while your running code can still reach it, however many methods return.
VendingMachine lobby = new VendingMachine(4); // machine A on the heap
lobby = new VendingMachine(10); // machine B; nothing refers to A now
lobby = null; // nothing refers to B either
null means “refers to nothing”. An object your running code can no longer reach, by following references from its variables, is unreachable, and the garbage collector, the part of the Java runtime that frees heap memory, may then reclaim it, fields and all. Java does not promise when, or even that it happens before the program ends; System.gc() is only a request. You never free memory yourself.
Do not paste it. Add a method refund() that returns the current credit and sets the credit back to 0. You need one local variable: work out why first. Test it with 3 quarters and 1 dime.
Show the answer
Save the credit in a local before clearing the field, or the value is lost:
VendingMachine.javapublic class VendingMachine {
private int credit;
private int stock;
public VendingMachine(int stock) {
this.stock = stock;
}
public void insertCoins(int quarters, int dimes) {
credit = credit + quarters * 25 + dimes * 10;
}
public int getCredit() {
return credit;
}
public int refund() {
int back = credit; // save it first
credit = 0; // then clear the field
return back;
}
public static void main(String[] args) {
VendingMachine lobby = new VendingMachine(4);
lobby.insertCoins(3, 1);
System.out.println("Refund: " + lobby.refund());
System.out.println("Credit now: " + lobby.getCredit());
}
}
Refund: 85
Credit now: 0The local back lives only for the call, which is all it needs: return hands its value over before the frame is popped.
What was new in this guide#
- Scope is where a name can be used; lifetime is how long the variable exists.
- The block rule: a local is in scope from its declaration to its block’s closing curly brace, or you get
cannot find symbol. - Parameters are in scope in their whole method, fields across the whole class (a static method reaches them through an object); a same-named parameter shadows a field, and
thisreaches past it. - Each call pushes a stack frame of parameters and locals; returning pops it. A stack trace prints the frames.
- Objects live on the heap; a reference variable holds an address, so
b = ashares one object. On references,==asks “same object?”; identical String literals share one object, andnew Stringmakes another. - Java passes a copy of every argument, including a copy of a reference.
- Locals die when the method returns; unreachable objects may be garbage collected, at a time Java chooses.
Scope is where a name can be used, from its declaration to the closing curly brace of its block; lifetime is how long the variable exists. Locals and parameters live in their call’s stack frame and die when the method returns; objects live on the heap, reached through references, and once nothing can reach them the garbage collector may reclaim them, at a time Java chooses.
Where these go wrong#
- Using a variable after the block that declared it. That is
cannot find symbol. Declare it before the block and only assign it inside. - Expecting a local to remember its value between calls. Each call gets a fresh one. A value that must survive has to be a field.
- Writing
stock = stock;when the parameter is also calledstock. The parameter shadows the field. Writethis.stock = stock;. - Thinking
b = acopies an object. It copies the reference, so both names share one object. Count thenews. - Saying Java passes objects by reference. It passes a copy of the reference. Pointing the parameter at a new object does nothing to the caller.
- Saying an object is destroyed when its method returns. Only the frame goes. An unreachable object may be reclaimed by the garbage collector, at a time Java chooses.
On the exam: For scope, slide your finger from the declaration down to the matching closing curly brace. For objects, count the news and draw one box per object. For the stack, write the frames bottom up in call order. Read the question twice, answer, then reread it to check every part.
Practice#
Answer each one on paper before you look at the solutions. Every answer that involves running code was checked by compiling and running it.
- This does not compile. Which line does the compiler point at, and why? Fix it so it prints the last value of
coinand the total.int total = 0; for (int coin = 5; coin <= 25; coin = coin + 10) { total = total + coin; } System.out.println(coin + " " + total); - What does this print? Count the
news first.Locker mine = new Locker(3); Locker hers = mine; mine = new Locker(1); hers.items = 7; System.out.println(mine.items + " " + hers.items); - In a
FareCardclass,tap(int fare)addsfareto a localsum(which starts at 0) and to a fieldtotal, then prints both.maincallsmine.tap(5),mine.tap(3), thenyours.tap(2)on a second card. Predict the three lines. maincallscheckout(3).checkoutdeclares a localsubtotal, then callstax(subtotal). Whiletaxruns: list the frames from top to bottom, say whethertaxcan use the namesubtotal, and say whethersubtotalstill exists.- Write an instance method
transferTo(VendingMachine other, int cans)that moves that many cans from this machine tootherand returnstrue, or changes nothing and returnsfalseif this machine does not have that many (orcansis negative). - In
main:VendingMachine m = new VendingMachine(4);thenVendingMachine spare = m;thenm = null;. Can the machine be garbage collected now? What changes afterspare = null;, and when will its memory be freed?
Solutions#
- The
printlnline.coinwas declared in the parentheses of thefor, so its scope ends at the loop’s closing curly brace:Coins.javapublic class Coins { public static void main(String[] args) { int total = 0; for (int coin = 5; coin <= 25; coin = coin + 10) { total = total + coin; } System.out.println(coin + " " + total); } }
DeclareCoins.java:7: error: cannot find symbol System.out.println(coin + " " + total); ^ symbol: variable coin location: class Coins 1 errorcoinbefore the loop and only assign it in the loop header. The loop stops whencoinreaches 35, the first value past 25:Coins.javapublic class Coins { public static void main(String[] args) { int total = 0; int coin; for (coin = 5; coin <= 25; coin = coin + 10) { total = total + coin; } System.out.println(coin + " " + total); } }35 45 1 7. Twonews, so two lockers.hers = mineshares the first. Thenminemoves to the second locker, sohers.items = 7changes only the first:Lockers.javaclass Locker { int items; Locker(int items) { this.items = items; } } public class Lockers { public static void main(String[] args) { Locker mine = new Locker(3); Locker hers = mine; mine = new Locker(1); hers.items = 7; System.out.println(mine.items + " " + hers.items); } }1 75 5,3 8,2 2. The localsumstarts at 0 on every call.totalis a field, so it keeps growing formine, while the second card has its owntotal:FareCard.javapublic class FareCard { private int total; public void tap(int fare) { int sum = 0; sum = sum + fare; total = total + fare; System.out.println(sum + " " + total); } public static void main(String[] args) { FareCard mine = new FareCard(); mine.tap(5); mine.tap(3); FareCard yours = new FareCard(); yours.tap(2); } }5 5 3 8 2 2- From the top:
tax,checkout,main.taxcannot use the namesubtotal; it has only its own parameter, a copy of the value. Butsubtotalstill exists incheckout’s paused frame. Out of scope but still alive. - Plain
stockis this machine’s field;other.stockfollows the reference to the other machine. Code inside a class may use the private fields of any object of that class:VendingMachine.javapublic class VendingMachine { private int stock; public VendingMachine(int stock) { this.stock = stock; } public int getStock() { return stock; } public boolean transferTo(VendingMachine other, int cans) { if (cans < 0 || cans > stock) { return false; // cannot move what is not here } stock = stock - cans; // this machine's field other.stock = other.stock + cans; // the other machine's field return true; } public static void main(String[] args) { VendingMachine lobby = new VendingMachine(10); VendingMachine gym = new VendingMachine(2); System.out.println(lobby.transferTo(gym, 4)); System.out.println(lobby.transferTo(gym, 40)); System.out.println(lobby.getStock() + " " + gym.getStock()); } }true false 6 6 - Not yet:
sparestill refers to it. Afterspare = null;it is unreachable, so the garbage collector may reclaim it. Java makes no promise about when, or even that it happens before the program ends.
Test-readiness checklist#
These are the items from the course’s Exam 1 topic list that this guide covers. Tick each one only when you can do it without looking.
- ☐ I can say where a local variable, a parameter and an instance variable are in scope.
- ☐ I can explain scope versus lifetime.
- ☐ I can draw the call stack for a chain of calls and say what each frame holds.
- ☐ I can say what is in the stack frame and what is on the heap for a line like
VendingMachine m = new VendingMachine(4);. - ☐ I can predict what prints when two references share one object, and say when an object can be collected.
Test yourself in the free Kestrel Exams app
18 practice questions on scope and memory, with every code answer checked by compiling and running it. Free, offline, no account.
Practice scope and memory →Frequently asked questions#
What is variable scope in Java?
Scope is the region of code where a variable's name can be used. A local variable is in scope from its declaration to the closing curly brace of its block, a parameter in its whole method, and an instance variable in the whole class, though a static method such as main must reach it through an object. Outside its scope, the name gives the compile error cannot find symbol.
What is the difference between scope and lifetime in Java?
Scope is about the program text: which lines may use the name. Lifetime is about time: how long the variable exists while the program runs. While method a waits for method b, a's locals are out of scope in b but still alive.
What is the difference between the stack and the heap in Java?
The call stack holds one frame per unfinished method call, with that call's parameters and local variables. The heap holds every object, whether made with new, an array, or a String literal, with its fields. In VendingMachine m = new VendingMachine(4); inside a method, the variable m is in the stack frame and the object is on the heap.
Is Java pass by value or pass by reference?
Always pass by value. For an object, the parameter receives a copy of the reference, so the method can change the object, but assigning a new object to the parameter does not change the caller's variable.
When is an object garbage collected in Java?
Once running code can no longer reach it, an object is eligible. The garbage collector may then reclaim it, but Java does not promise when; System.gc() is only a request.
Something here not clear? A topic you wish we covered? Tell us. We read every message, and a request is the fastest way to get a guide written: several of these exist because somebody asked.