user2745108
user2745108

Reputation: 55

How can I make this class immutable despite having a mutable member?

I want to make the following class immutable, even though it has a mutable List member. How can I achieve this?

class MyImmutableClass {
        private List<Integer> data;
        public MyImmutableClass(List<Integer> data) {
            this.data = data;
        }
        public List<Integer> getData() {
            return data;
        }
    }

Below is test class whose main() function that modifies object state.

class TestMyImmutableClass{
    public static void main(String[] args) {
        List<Integer> data = new ArrayList<Integer>();
        data.add(2);
        data.add(5);
        MyImmutableClass obj = new MyImmutableClass(data);
        obj.getData().add(3);
        System.out.println("Data is; " + obj.getData());
    }
}

O/P : Data is; [2, 5, 3]

Upvotes: 4

Views: 272

Answers (3)

Craig P. Motlin
Craig P. Motlin

Reputation: 26740

If you're using Eclipse Collections, you can change the type of MyImmutableClass.data to ImmutableList.

class MyImmutableClass {
    private ImmutableList<Integer> data;
    public MyImmutableClass(List<Integer> data) {
        this.data = Lists.immutable.withAll(data);
    }
    public ListIterable<Integer> getData() {
        return data;
    }
}

Eclipse Collection's ImmutableList and ListIterable types are contractually immutable, meaning they don't have add() and remove() methods. This way, it's clear from the return type of getData() that the returned list cannot be changed.

Both ImmutableList and MutableList extend ListIterable so you could change the implementation from an ImmutableList to an unmodifiable list without changing the API.

class MyImmutableClass {
    private MutableList<Integer> data;
    public MyImmutableClass(List<Integer> data) {
        this.data = Lists.mutable.withAll(data);
    }
    public ListIterable<Integer> getData() {
        return data.asUnmodifiable();
    }
}

Note: I am a committer for Eclipse collections.

Upvotes: 2

user949300
user949300

Reputation: 15729

In your constructor, go

this.data = Collections.unmodifiableList(new ArrayList(data));

For completeness and clarity, declare the data field as final.

Upvotes: 6

MadProgrammer
MadProgrammer

Reputation: 347244

In you getData method, instead of returning a reference to your List like...

public List<Integer> getData() {
    return data;
}

You could return an unmodifiable List instead

public List<Integer> getData() {
    return Collections.unmodifiableList(data);
}

See Collections.unmodifiableList for more details...

Updated

As pointed out by user949300, you should also make a copy of the original list.

public MyImmutableClass(List<Integer> data) {
    this.data = new ArrayList<Integer>(data);
}

This will prevent any one who has access to the original list from making modifications to it.

Upvotes: 7

Related Questions