Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How do I use collections in immutable class safely in Java?

I try to implement immutable class, and I see a rule stating "Perform cloning of objects in the getter methods to return a copy rather than the returning actual object reference".

I understand that when we use immutables there would be no change in copied / cloned collections returned from the getters. When we use custom classes, the change in original collection can be seen also cloned ( shallow copied ) collection return from the getters.

In below code, I could not understand the case :

I created two methods, one for return the original collection as courseList and one for shallow copy of the courselist.

I assigned two version to local references clist1 and clist2.

Then I changed the item in original list. I can see the change original list and copied list also when I reach them through student object. However the change cannot be seen throug the reference I pointed to the cloned course list before ! I think it should also be affected by the change. Why I cant see the change on previously copied version ? This is reference and I think it should be point the same memory area, I also check the result by another example below again. I created a list containing StringBuilder. I appeded new strs to stringbuilder and then I can see the changed previously copied version of the list.

So, the main question, must I use the deep copy in immutable classes always ? Is this a wrong usage ? What would be the safe way to use collections in immutable classes ?

Thanks in advance.

ImmutableStudent.java


public final class ImmutableStudent {

    public ImmutableStudent(String _name, Long _id, String _uni, ArrayList<String> _courseList){
        name = _name;
        id = _id;
        uni = _uni;
        courseList = new ArrayList<>();
        _courseList.forEach( course -> courseList.add(course));
    }

    private final String name;
    private final Long id;
    private final String uni;
    private final ArrayList<String> courseList;


    public String getName() {
        return name;
    }

    public Long getId() {
        return id;
    }

    public String getUni() {
        return uni;
    }


    public List<String> getCourseList() {
        return courseList;
    }

    public List<String> getCourseListClone() {
        return (ArrayList<String>)courseList.clone();
    }
    
}

ImmutableHelper.java

public class ImmutableHelper {

    public static void run(){

        ArrayList<String> courseList = new ArrayList<>();
        courseList.add("Literature");
        courseList.add("Math");

        String name = "Emma";
        Long id = 123456L;
        String uni = "MIT";

        ImmutableStudent student = new ImmutableStudent(name, id, uni, courseList);

        System.out.println(name == student.getName());
        System.out.println(id.equals(student.getId()));
        System.out.println(courseList == student.getCourseList());

        System.out.println("Course List         :" + student.getCourseList());
        System.out.println("Course List Clone   :" + student.getCourseListClone());

        List<String> clist1 = student.getCourseList();
        List<String> clist2 = student.getCourseListClone();

        student.getCourseList().set(1, "Art");

        System.out.println("Course List         :" + student.getCourseList());
        System.out.println("Course List Clone   :" + student.getCourseListClone());

        System.out.println("Previous Course List        :" + clist1);
        System.out.println("Previous Course List Clone  :" + clist2);
        

        // Check shallow copy using collections.clone()


        ArrayList<StringBuilder> bList = new ArrayList<>();

        StringBuilder a = new StringBuilder();
        a.append("1").append("2").append("3");

        StringBuilder b = new StringBuilder();
        b.append("5").append("6").append("7");

        bList.add(a);
        bList.add(b);

        ArrayList<StringBuilder> bListCp = (ArrayList<StringBuilder>)bList.clone();

        System.out.println("Blist   : " + bList);
        System.out.println("BlistCp :" + bListCp);

        a.append(4);

        System.out.println("Blist   : " + bList);
        System.out.println("BlistCp :" + bListCp);

    }
}

The Result

Course List         :[Literature, Math]

Course List Clone   :[Literature, Math]

Course List         :[Literature, Math, Art]

Course List Clone   :[Literature, Math, Art]

Previous Course List        :[Literature, Math, Art]

Previous Course List Clone  :[Literature, Math]

Blist   : [123, 567]

BlistCp :[123, 567]

Blist   : [1234, 567]

BlistCp :[1234, 567]
like image 917
coderates Avatar asked Nov 24 '25 15:11

coderates


1 Answers

From the clone() Javadoc:

Returns a shallow copy of this ArrayList instance. (The elements themselves are not copied.)

What this means is that the reference returned by the clone method is actually a reference to a new instance of ArrayList that contains exactly the same elements as the original list. In an example:

// Original list is reference L1 and contains three elements A, B and C
L1 = [ A, B, C ]

// By doing L1.clone you get a reference to a new list L2
L2 = [ A, B, C ]

// When you add a new element to L1 you do not see the change in L2 because
// it is effectively a different list
L1 = [ A, B, C, D ]
L2 = [ A, B, C ]

// However, if you change one of the A, B or C's internal state then that
// will be seen when accessing that object through L2, since the reference
// kept in the lists are the same
L1 = [ A, B', C, D ]
L2 = [ A, B', C ]

For your question:

So, the main question, must I use the deep copy in immutable classes always ? Is this a wrong usage ? What would be the safe way to use collections in immutable classes ?

It depends on what you want to achieve. There are two scenarios:

Scenario A: You want the users of the class to receive an immutable view of the list (meaning no user can modify the list) but you want any changes that happen to the original list to be propagated through the immutable view.

Scenario B: You want all versions of the list to be immutable, even the internally kept one.

Both scenarios can be answered by using Collections.unmodifiableList, which states in the Javadoc:

Returns an unmodifiable view of the specified list. Query operations on the returned list "read through" to the specified list, and attempts to modify the returned list, whether direct or via its iterator, result in an UnsupportedOperationException.

The difference would be on where you use it. For Scenario A you would invoke the method in the getter, so that every caller would receive the unmodifiable view of the list but the class would still keep the modifiable view internally. For Scenario B you would store a reference to the result of calling unmodifiableList internally in the class. Note that on newer versions of Java you can also use List.copyOf to create an immutable list, which you could use for Scenario B.

From your description I believe what you are trying to achieve is scenario A.

like image 165
rpsrosario Avatar answered Nov 27 '25 05:11

rpsrosario



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!