Home > front end >  Why changes in sublist are reflected in the original list in Java... how to avoid this?
Why changes in sublist are reflected in the original list in Java... how to avoid this?

Time:11-01

// "static void main" must be defined in a public class.
import java.util.*;
public class Main {
    static void func(ArrayList<ArrayList<Integer>> arr) {
        ArrayList<Integer> arr1 = new ArrayList();
        arr1.add(0);
        arr.add(arr1);
        arr1.set(0,5);
    }
    public static void main(String[] args) {
        ArrayList<ArrayList<Integer>> arr = new ArrayList<ArrayList<Integer>>();
        func(arr);
        for(int i=0;i<arr.size();i  ) {
            for(int j=0;j<arr.get(i).size();j  ) {
                System.out.print(arr.get(i).get(j));
            }
            System.out.println();
        }
    }
}

In the above code, we add an 1D arraylist of 1 element (0) to the 2D arraylist arr, but when we change the value in the 1D arraylist from 0 to 5, why is the same change reflected in the 2d arraylist, and most importantly, how exactly do we avoid this?

CodePudding user response:

By default, Java Lists are mutable and you can easily change their content by adding or removing elements. In addition to this, your arr List has a reference to arr1 and not a copy of it. This means that whenever you change 1D this change is also reflected in arr because both arr1 and the content of arr reference to the same exact List.

One way to avoid this is actually copying the arr1 List before adding it to arr as follows:

static void func(ArrayList<ArrayList<Integer>> arr) {
    ArrayList<Integer> arr1 = new ArrayList<>();
    arr1.add(0);
    arr.add(new ArrayList<>(arr1));
    arr1.set(0,5);
}

public static void main(String[] args) {
    ArrayList<ArrayList<Integer>> arr = new ArrayList<ArrayList<Integer>>();
    func(arr);
    for(int i=0;i<arr.size();i  ) {
        for(int j=0;j<arr.get(i).size();j  ) {
            System.out.print(arr.get(i).get(j));
        }
        System.out.println();
    }
}

Mind the changed line arr.add(new ArrayList<>(arr1));. This guarantees that the reference that arr holds is different than the reference of the original arr1. You are basically creating a new List.

Although this fixes your case, it might not work for every List because it depends on its content. This works on your case because Integer is an immutable class, but if you would have a List of a mutable class, then this wouldn't be enough because both Lists would reference exactly the same objects. An example of this is below:

public static void main(String[] args) {
    ArrayList<ArrayList<MutableClass>> arr = new ArrayList<>();
    ArrayList<MutableClass> arr1 = new ArrayList<>();
    arr1.add(new MutableClass("original"));
    arr.add(new ArrayList<>(arr1));

    System.out.println(arr.get(0));

    // now let's change the property of arr1 MutableClass to something else
    arr1.get(0).setProperty("changed");
    System.out.println(arr.get(0));
}

public static class MutableClass {
    private String property;

    public MutableClass(String property) {
        this.property = property;
    }

    public String getProperty() {
        return property;
    }

    public void setProperty(String property) {
        this.property = property;
    }

    @Override
    public String toString() {
        return "MutableClass{"  
                "property='"   property   '\''  
                '}';
    }
}

As you can see by running the code, changing the object in arr1, also changes the object in arr even if you create a new List. The only way to overcome this is by making MutableClass immutable or by deep copying the original arr1 List before adding it to arr. This means you would need to copy every single object inside arr1 as follows:

public static void main(String[] args) {
    ArrayList<ArrayList<MutableClass>> arr = new ArrayList<>();
    ArrayList<MutableClass> arr1 = new ArrayList<>();
    arr1.add(new MutableClass("original"));

    ArrayList<MutableClass> copiedList = new ArrayList<>();
    for (MutableClass object : arr1) {
        copiedList.add(new MutableClass(object));
    }
    arr.add(copiedList);

    System.out.println(arr.get(0));

    // now let's change the property of arr1 MutableClass to something else
    arr1.get(0).setProperty("changed");
    System.out.println(arr.get(0));
}

public static class MutableClass {
    private String property;

    public MutableClass(String property) {
        this.property = property;
    }

    public MutableClass(MutableClass other) {
        this.property = other.property;
    }

    public String getProperty() {
        return property;
    }

    public void setProperty(String property) {
        this.property = property;
    }

    @Override
    public String toString() {
        return "MutableClass{"  
                "property='"   property   '\''  
                '}';
    }
}

CodePudding user response:

To avoid 5 getting reflected in 2D list create a new array list from arr1 in method func() and add it to arr(the 2D list) as shown below.

static void func(ArrayList<ArrayList<Integer>> arr) {
    ArrayList<Integer> arr1 = new ArrayList();
    arr1.add(0);
    arr.add(new ArrayList<>(arr1));
    arr1.set(0,5);
}

The reason for the behaviour:

  • Java is pass by value

  • When we do

    func(arr); in main method, the reference to the actual array list object in heap is passed as value to the method "func()".

Inside func, hence arr will be pointing to the same object.

When we add the array list newly created arr1 into arr, the reference to actual object corresponding to arr1 gets added to the actual 2D array. This is because both arr of func() as well as arr of main() refers to same object in heap.

When we try to change something in arr1 list, it is referenced by arr. So, it will be still visible to arr. Printing the list in main gives output 5.

But when we create a new array list and add that to arr in func(), as below

arr.add(new ArrayList<>(arr1));

then the list got added to arr will be different from arr1. Hence, while printing in main(), the 2D list holds the reference to newly created array list (ie; new ArrayList<>(arr1)).

So, it will not get affected by changing to 5 by the line

arr1.set(0,5);

This will still give 0 while printing the list arr in main().

  • Related