Pass by Value in Java
Java always passes arguments by value. For objects the value copied is the reference, which explains every result that looks like pass by reference.
-
Java Basics
- Introduction to Java
- Setting Up Java and Writing Your First Program
- Variables, Data Types and Literals in Java
- Type Casting and Type Conversion in Java
- Operators and Expressions in Java
- Input and Output in Java
- Comments, Keywords and Naming Conventions in Java
- Control Flow in Java: if, else and switch
- Loops in Java: for, while and do-while
- Methods
- Arrays and Strings
-
OOP
- Classes and Objects in Java
- Constructors in Java
- The this Keyword in Java
- The static Keyword in Java
- Encapsulation in Java
- Access Modifiers in Java
- Inheritance in Java
- Method Overriding and super in Java
- Polymorphism in Java
- Abstraction, Abstract Classes and Interfaces in Java
- Composition, Aggregation and Association in Java
- The Object Lifecycle in Java
- Core Java
- Exception Handling
-
Collections
- The Java Collections Framework
- List in Java: ArrayList, LinkedList, Vector and Stack
- Set in Java: HashSet, LinkedHashSet and TreeSet
- Map in Java: HashMap, LinkedHashMap and TreeMap
- How HashMap Works Internally in Java
- Queue and Deque in Java: ArrayDeque and PriorityQueue
- Iterators in Java
- Comparable and Comparator in Java
- Collections Utilities and Choosing the Right Collection
- Generics
- Java 8+
- Stream API
- Date and Time
- File and I/O
-
Multithreading
- Threads in Java: Processes, Runnable and Thread
- Thread Lifecycle in Java
- Synchronization in Java: synchronized and volatile
- Locks and Atomic Classes in Java
- Race Conditions and Deadlocks in Java
- The Executor Framework and Thread Pools in Java
- Future and CompletableFuture in Java
- Concurrent Collections in Java
- The Java Memory Model
- JVM and Memory
- Advanced Java
- Networking
- JDBC
- Testing
The rule
Java is always pass by value. There is no exception and no reference passing mode. What varies is what the value is: for a primitive it is the number itself, and for an object it is the reference.
Primitives
public static void increase(int number) {
number = number + 10; // changes the local copy only
}
int score = 5;
increase(score);
System.out.println(score); // 5The method received a copy of the value 5. Reassigning that copy cannot reach the caller.
Objects: the part that confuses everyone
public static void rename(StringBuilder text) {
text.append(" updated"); // changes the object both names point to
}
StringBuilder message = new StringBuilder("draft");
rename(message);
System.out.println(message); // draft updatedThis looks like pass by reference, but it is not. The method received a copy of the reference. Both copies point at the same object, so a change made through either one is visible to both.
The test that settles it
public static void replace(StringBuilder text) {
text = new StringBuilder("something else"); // repoints the local copy
}
StringBuilder message = new StringBuilder("draft");
replace(message);
System.out.println(message); // draft - unchangedIf Java were pass by reference, reassigning the parameter would replace the caller variable too. It does not. That single example is the clearest proof, and it is worth remembering for interviews.
A picture of what happens
caller: message ---> [ StringBuilder: "draft" ]
^
method: text ---------------| (a second reference, same object)
text.append(...) changes the object -> caller sees it
text = new ... changes only the arrow -> caller sees nothingWhy immutability changes the feel
public static void shout(String word) {
word = word.toUpperCase(); // creates a new String, rebinds the local copy
}
String greeting = "hello";
shout(greeting);
System.out.println(greeting); // helloString has no method that changes its contents, so the only thing a method can do is rebind its own copy. That is why strings feel like primitives when passed around, even though they are objects.
Arrays
public static void zeroFirst(int[] data) {
data[0] = 0; // visible to the caller
data = new int[]{9, 9, 9}; // not visible to the caller
}An array is an object, so the same two rules apply: changing an element is shared, replacing the array is not.
Returning results instead
// Instead of trying to modify the parameter
public static int increased(int number) {
return number + 10;
}
int score = increased(5); // 15Java has no output parameters, so a method that must produce a new value should return it. When several values are needed, return a small record.
record Split(int quotient, int remainder) { }
public static Split divide(int a, int b) {
return new Split(a / b, a % b);
}Common mistakes
- Believing Java passes objects by reference. It passes references by value, which is a different statement.
- Expecting a swap method to work. In Java it cannot swap the caller variables.
- Modifying a caller collection inside a method as a hidden side effect.
- Assuming a
finalparameter makes the object immutable. It only prevents rebinding the parameter.
Best practices
- Prefer returning new values over modifying arguments.
- If a method does modify an argument, say so in the name and in the documentation.
- Copy a mutable argument defensively when the object must not be shared.
- Use records to return more than one value instead of filling an array supplied by the caller.
Practice
- Write a
swap(int a, int b)method and explain why it cannot work as intended. - Predict the output when a method calls
list.add("x")and thenlist = new ArrayList<>(). - Why does passing a
Stringfeel different from passing aStringBuilder? - Rewrite a method that fills a caller supplied array so that it returns a new array instead, and state one advantage.
Conclusion
Java copies the argument, always. For objects the copy is the reference, so shared changes travel and reassignment does not. Reassigning a parameter is the experiment that proves it.