25 · 32 · 5 min
What a variable actually holds
A variable holding an object does not hold the object. It holds a reference to it, and almost every surprising assignment result follows from that.
Stack and heap
void process() {
int count = 5; // the value, on the stack
Order o = new Order(); // object on the heap, reference on the stack
}The stack frame disappears when the method returns. The heap object survives as long as something references it.
Assignment copies the reference
Order a = new Order("x");
Order b = a;
b.setId("y");
a.getId(); // "y": one object, two referencesPassing to a method
Java is always pass by value. The subtlety is that the value being passed is the reference.
void rename(Order o) { o.setId("changed"); } // caller sees it
void replace(Order o) { o = new Order("new"); } // caller sees nothingThe first modifies the object both references point at. The second points the local parameter somewhere else, which the caller's variable knows nothing about.
This one distinction explains most confusion about "does Java pass by reference". It does not; it passes references by value.
| Language | Passes |
|---|---|
| Java | always by value; object variables are references |
| Python | references by value, same behaviour |
| C++ | by value by default, by reference with & |
| C | by value; pass a pointer for the same effect |
The mutable default argument
def add(item, items=[]): # the list is created once, at definition
items.append(item)
return items
add("a") # ['a']
add("b") # ['a', 'b'] not a fresh listThe default is one object shared by every call that omits the argument. The fix is items=None and creating the list inside.
null
A reference pointing at nothing. Dereferencing it is the single most common run time failure in these languages, and its inventor called it his billion dollar mistake.
Modern answers: Optional in Java, nullable types in Kotlin and C# where the compiler forces a check, and Option in Rust where there is no null at all.