Search code examples
javalambdajava-8pass-by-reference

Object is not updated (by Lambdas filter) when passing it (by reference) to a method


I'm using java8, SpringBoot 2, and JPA2 and I'm run in a strange problem. Suppose the following code:

public void doSome() {
    List<Integer> arr = new ArrayList<>(Arrays.asList(1,2,3,4,5));
    List<Integer> arr2 = new ArrayList<>(Arrays.asList(1,2,3,4));

    otherMethod(arr, arr2);
    System.out.println(arr);
}

In the same class I have the otherMethod() and with this version:

public void otherMethod(List<Integer> arr, List<Integer> arr2) {
    arr = arr.stream().filter(x -> !arr2.contains(x)).collect(Collectors.toList());
    System.out.println(arr); // here print just 5
}

When code flow bring me back to doSome():

System.out.println(arr); // print [1,2,3,4,5]

If I change my otherMethod() as follows:

public void otherMethod(List<Integer> arr, List<Integer> arr2) {
//    arr = arr.stream().filter(x -> !arr2.contains(x)).collect(Collectors.toList());
    arr.removeIf(x -> x < 3);
    System.out.println(arr); // print [3,4,5]
}

and the changes affect the Object I have passed by reference, in doSome() method I see:

System.out.println(arr); // print [3,4,5]

I don't understand why with arr.stream().filter(x -> !arr2.contains(x)).collect(Collectors.toList()) changes doesn't affect my Object passed by reference.

where am I wrong?


Solution

  • First thing I want to say is Java has only pass-by-value and no pass-by-reference.

    This is clearly mentioned in this post

    Lets walkthrough your code.

    public void doSome() {
        //Memory is allocated in heap for two new array lists 
        // and the address of each copied to arr, arr2 variables respectively.
    
        List<Integer> arr = new ArrayList<>(Arrays.asList(1,2,3,4,5)); 
        List<Integer> arr2 = new ArrayList<>(Arrays.asList(1,2,3,4));
    
        // Here you are passing the value stored in arr and arr2 variables.
        // And the called method copies the values to another set of variables defined as parameters in the called method.
        otherMethod(arr, arr2);
    
        // Print the content of the object(ArrayList) whose address is stored in arr variable.
        System.out.println(arr);
    } 
    
    public void otherMethod(List<Integer> arr, List<Integer> arr2) {
        // When this method is called, Two variable are created. The value of parameters from the calling method will be copied to these variables.
    
        //Here Collectors.toList() creates a new list after going through stream and filter.
        // And then you are assigning the new list to arr varibable.
        //Which means arr stores the reference to newly created list and loses the reference to original list.
        // This statement will not affect the arr variable in doSome()
        arr = arr.stream().filter(x -> !arr2.contains(x)).collect(Collectors.toList());
    
        //Printing the value of list and this list is different from the called list
        System.out.println(arr); // here print just 5
    }
    

    arr.removeIf(x -> x < 3); in Other method takes effect in doSome() because, the arr is still pointing to same heap location.

    If you want the the modified list in doSome() method, you have two options.

    1. return the new arraylist form the calling method and use it in calling method.
    2. Or Copy the new filtered list to another temp variable. Clear the list pointed by arr variable and then add all items from filtered list to arr variable.