Pass by Value vs. Pass by Reference
A common point of confusion in Java is how arguments are passed to methods. To be precise, Java is always a pass-by-value language.
- Java always passes a copy of whatever is stored in the variable. For primitives, it passes a copy of the actual value. For objects, it passes a copy of the memory address (the reference).
However, the behavior appears different for primitive types and object types.
- For Primitive Types: The method receives a copy of the actual value (e.g., a copy of the number 10).
- For Object Types: The method receives a copy of the reference (the memory address).
Because the method gets a copy of the memory address, it is pointing to the exact same object as the original variable. This is why it's often informally called "pass-by-reference," as the method can modify the original object's state.
Reference Variables
You already know that data types are broadly divided into primitive types and object (reference) types. Primitives include: byte, char, short, int, long, float, boolean, and double. Everything else is an object.
A reference variable holds the memory address of an object, not the object itself. It "points" to the object in memory. While Java is technically always pass-by-value, the behavior differs because the "value" being passed for an object is the reference (address) itself.
When sending variables as arguments to a method or constructor:
- If primitive variables are used, the literal values they hold are copied to the method's parameters.
- If an object is passed, the reference (the address) is copied to the parameter. This means both the original variable and the parameter point to the exact same object in memory.
Task: Tracing pass-by-value vs pass-by-reference behavior.
package com.mbcc.tutorial.chapter4;
public class PassingByReferenceValue {
public static void main(String[] args) {
int a = 10;
MyObject myObject = new MyObject();
myObject.setA(10);
doubleMe(a, myObject);
System.out.println("a value inside main method = " + a);
System.out.println("a value of object inside main method = " + myObject.getA());
}
public static void doubleMe(int a, MyObject m) {
a = a * 2;
System.out.println("a value inside doubleMe method = " + a);
m.setA(m.getA() * 2);
System.out.println("a value of object inside doubleMe method = " + m.getA());
}
}
class MyObject {
int a;
public int getA() {
return a;
}
public void setA(int a) {
this.a = a;
}
}
Run the program above. What do you notice, and why?
As you can see, the primitive argument a is copied to the parameter in the doubleMe method (pass-by-value). Changes made to a inside the method do not affect the original variable in main.
However, for MyObject, the reference is passed. This means the method receives a copy of the address pointing to the object. Because both the main method and the doubleMe method are looking at the same object, any changes made to the object's attributes inside the method (like m.setA(...)) are reflected in the original object.
Key Takeaways:
- Pass-by-Value: The method receives a copy of the data. Modifying the copy does not affect the original. This applies to all primitives.
- Pass-by-Reference (Behavior): For objects, Java passes the reference (address) by value. Since the method gets the address of the original object, it can modify that object's state.
The main difference is that modifications made to an object's state inside a method will persist after the method finishes, whereas modifications to a primitive parameter will not.