Module 4: Pass-by-value

Module Overview

Java is strictly a pass-by-value language, which means that when you pass variables to methods, the method receives a copy of the value, not a reference to the original variable. Understanding this concept is critical for effective Java programming and avoiding common bugs.

Learning Objectives

  • Understand Java's parameter passing mechanism (pass-by-value)
  • Differentiate between primitive and reference type parameter behavior
  • Explain why "Java is always pass-by-value"
  • Predict the effects of method parameter modifications
  • Apply pass-by-value principles to write robust methods
  • Understand common misconceptions about Java's parameter passing

Pass-by-value Fundamentals

What is Pass-by-value?

Pass-by-value means that when you pass a parameter to a method, the method receives a copy of the value, not the original variable itself. Any changes to this copy inside the method do not affect the original variable outside the method.

Pass-by-value with Primitive Types

Primitive types in Java (like int, boolean, char, etc.) are passed by value, meaning a copy of the actual value is passed to the method.


public static void main(String[] args) {
    int x = 10;
    System.out.println("Before: x = " + x);  // Outputs: 10
    modifyValue(x);
    System.out.println("After: x = " + x);   // Still outputs: 10
}

public static void modifyValue(int num) {
    num = num * 2;  // Changes only the local copy
    System.out.println("Inside method: num = " + num);  // Outputs: 20
}
                    

In this example, the value of x remains 10 even after calling modifyValue() because only a copy of x's value was modified inside the method.

Pass-by-value with Reference Types

For reference types (objects), Java passes a copy of the reference, not the actual object. This means the method works with the same object as the caller, but cannot make the caller's variable reference a different object.


public static void main(String[] args) {
    int[] numbers = {1, 2, 3};
    System.out.println("Before: numbers[0] = " + numbers[0]);  // Outputs: 1
    modifyArray(numbers);
    System.out.println("After: numbers[0] = " + numbers[0]);   // Outputs: 99
}

public static void modifyArray(int[] arr) {
    arr[0] = 99;  // Modifies the actual object
    
    // This reassignment affects only the local copy of the reference
    arr = new int[]{4, 5, 6};  
    System.out.println("Inside method after reassignment: arr[0] = " + arr[0]);  // Outputs: 4
}
                    

Here, the modification to arr[0] affects the original numbers array because both variables reference the same object. However, reassigning arr to a new array only affects the local copy of the reference.

Common Misconception: "Java is Pass-by-reference"

Many programmers mistakenly believe that Java passes objects by reference. This confusion arises because:

  • You can modify the internal state of objects passed to methods
  • This behavior seems similar to pass-by-reference in other languages

However, Java is strictly pass-by-value, but it passes a copy of the reference value for objects, which still provides access to the same object.

Demonstrating Pass-by-value

Example with a Custom Class


class Person {
    String name;
    
    public Person(String name) {
        this.name = name;
    }
    
    @Override
    public String toString() {
        return "Person{name='" + name + "'}";
    }
}

public class PassByValueDemo {
    public static void main(String[] args) {
        // Example with an object (reference type)
        Person person = new Person("Alice");
        System.out.println("Before modifyPerson: " + person);  // Person{name='Alice'}
        
        modifyPerson(person);
        System.out.println("After modifyPerson: " + person);   // Person{name='Alice (modified)'}
        
        replacePerson(person);
        System.out.println("After replacePerson: " + person);  // Still Person{name='Alice (modified)'}
    }
    
    public static void modifyPerson(Person p) {
        // This modifies the object that p refers to
        p.name = p.name + " (modified)";
    }
    
    public static void replacePerson(Person p) {
        // This only modifies the local reference p
        p = new Person("Bob");
        System.out.println("Inside replacePerson: " + p);  // Person{name='Bob'}
    }
}
                    

Key Insights

  1. In modifyPerson(), we changed a property of the object, which affects the original object
  2. In replacePerson(), we reassigned the parameter to a new object, but this had no effect on the original reference
  3. This behavior demonstrates that Java passes the value of the reference, not the reference itself

Practical Applications and Best Practices

  • Return modified values: If you need to change primitive values, return the modified value instead of trying to modify parameters
  • Use wrapper objects: When you need to modify a single value in a method, consider wrapping it in an object
  • Be careful with reassignment: Remember that reassigning parameter variables inside methods won't affect the original references
  • Document behavior: Clearly document whether your methods modify their parameters
  • Consider immutability: Use immutable objects when appropriate to avoid unintended modifications
  • Be aware of defensive copying: For sensitive operations, consider creating defensive copies of mutable objects

Visual Representation of Pass-by-value

Primitive Types


// Memory before calling modifyValue(x)
// -----------------------------------
// |  Variable  |  Memory   | Value  |
// -----------------------------------
// |      x     | Address1  |   10   |
// -----------------------------------

modifyValue(x);  // A copy of value 10 is passed

// Memory inside modifyValue(num)
// -----------------------------------
// |  Variable  |  Memory   | Value  |
// -----------------------------------
// |      x     | Address1  |   10   |
// |     num    | Address2  |   10   | (starts as a copy)
// -----------------------------------

// After num = num * 2;
// -----------------------------------
// |  Variable  |  Memory   | Value  |
// -----------------------------------
// |      x     | Address1  |   10   | (unchanged)
// |     num    | Address2  |   20   | (modified)
// -----------------------------------
                    

Reference Types


// Memory before calling modifyArray(numbers)
// --------------------------------------------
// |  Variable  |  Memory   |    Value       |
// --------------------------------------------
// |  numbers   | Address1  | [Ref to Addr2] |
// |   Array    | Address2  |   {1, 2, 3}    |
// --------------------------------------------

modifyArray(numbers);  // A copy of the reference is passed

// Memory inside modifyArray(arr)
// --------------------------------------------
// |  Variable  |  Memory   |    Value       |
// --------------------------------------------
// |  numbers   | Address1  | [Ref to Addr2] |
// |    arr     | Address3  | [Ref to Addr2] | (copy of reference)
// |   Array    | Address2  |   {1, 2, 3}    |
// --------------------------------------------

// After arr[0] = 99;
// --------------------------------------------
// |  Variable  |  Memory   |    Value       |
// --------------------------------------------
// |  numbers   | Address1  | [Ref to Addr2] |
// |    arr     | Address3  | [Ref to Addr2] |
// |   Array    | Address2  |   {99, 2, 3}   | (modified)
// --------------------------------------------

// After arr = new int[]{4, 5, 6};
// --------------------------------------------
// |  Variable  |  Memory   |    Value       |
// --------------------------------------------
// |  numbers   | Address1  | [Ref to Addr2] | (unchanged)
// |    arr     | Address3  | [Ref to Addr4] | (new reference)
// |  Array1    | Address2  |   {99, 2, 3}   |
// |  Array2    | Address4  |   {4, 5, 6}    | (new array)
// --------------------------------------------
                    

Additional Resources

Guided Projects