简体   繁体   中英

Immutable Java Class with List as field

Can we make the class immutable which has collection as one of the fields?

public class Student implements Comparable<Student> {

    private int rollNumber;
    private String name;
    private Set<String> subjects;
    private List<Integer> marks ;

    public Student(int rollNumber, String name, Set<String> subjects,
            List<Integer> marks) {
        this.rollNumber = rollNumber;
        this.name = name;
        this.subjects = Collections.unmodifiableSet(subjects);
        this.marks = Collections.unmodifiableList(marks);
        setPercentage();
    }

    private float percentage;

    public int getRollNumber() {
        return rollNumber;
    }

    public String getName() {
        return name;
    }

    public Set<String> getSubjects() {
        return new HashSet<>(subjects);
    }

    public List<Integer> getMarks() {
        return new ArrayList<>(marks);
    }

    public float getPercentage() {
        return percentage;
    }

    private void setPercentage() {
        float sum = 0;

        for (Integer i : marks)
            sum = sum + i;
        if (!marks.isEmpty())
            percentage = sum / marks.size();

    }
}

I am not able to achieve it.

I tried:

Set<String> subjects= new HashSet<>();
subjects.add("Maths");
subjects.add("Science");
subjects.add("English");
List<Integer> marks1= new LinkedList<Integer>();
marks1.add(45);
marks1.add(36);
marks1.add(98);
Student student1= new Student(1, "Payal", subjects, marks1);
//student1.getSubjects().add("History");
subjects.add("History");
System.out.println(student1);

But subjects.add is changing the state of the object.

Please help.

You're making a copy of both collections before returning them from your getters. This is unnecessary, since the collections are unmodifiable (unless you want the caller to get mutable collections and not unmodifiable ones).

What is necessary is to make copies of the collections that are passed from the outside in the contructor. Otherwise, the caller can still modify the collections after they've been stored in your object:

this.subjects = Collections.unmodifiableSet(new HashSet<>(subjects));
this.marks = Collections.unmodifiableList(new ArrayList<>(marks));

To be truly immutable, the class and its fields should also be final.

public class Student implements Comparable<Student> {

private int rollNumber;
private String name;
private Set<String> subjects;
private List<Integer> marks ;

public Student(int rollNumber, String name, Set<String> subjects,
        List<Integer> marks) {
    this.rollNumber = rollNumber;
    this.name = name;
    this.subjects = new HashSet<>(subjects);
    this.marks = new ArrayList<>(marks);
    setPercentage();
}

private float percentage;

public int getRollNumber() {
    return rollNumber;
}

public String getName() {
    return name;
}

public Set<String> getSubjects() {
    return new HashSet<>(subjects);
}

public List<Integer> getMarks() {
    return new ArrayList<>(marks);
}

public float getPercentage() {
    return percentage;
}

private void setPercentage() {
    float sum = 0;

    for (Integer i : marks)
        sum = sum + i;
    if (!marks.isEmpty())
        percentage = sum / marks.size();

}

}

I did this. It worked. If I try these 2 operations now, no problem.

    Set<String> subjects= new HashSet<>();
    subjects.add("Maths");
    subjects.add("Science");
    subjects.add("English");
    List<Integer> marks1= new LinkedList<Integer>();
    marks1.add(45);
    marks1.add(36);
    marks1.add(98);
    Student student1= new Student(1, "Payal", subjects, marks1);
    student1.getSubjects().add("History");
    subjects.add("History");
    System.out.println(student1);

Here is the answer

import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

public class Student implements Comparable<Student> {

    private final int rollNumber;
    private final String name;
    private final Set<String> subjects;
    private final List<Integer> marks;

    public Student(int rollNumber, String name, Set<String> subjects, List<Integer> marks) {
        this.rollNumber = rollNumber;
        this.name = name;
        this.subjects = new HashSet<>(subjects);
        this.marks = new ArrayList<>(marks);
        setPercentage();
    }

    private float percentage;

    public int getRollNumber() {
        return rollNumber;
    }

    public String getName() {
        return name;
    }

    public Set<String> getSubjects() {
        return Collections.unmodifiableSet(subjects);
    }

    public List<Integer> getMarks() {
        return new ArrayList<>(marks);
    }

    public float getPercentage() {
        return percentage;
    }

    private void setPercentage() {
        float sum = 0;
        for (Integer i : marks) {
            sum = sum + i;
        }
        if (!marks.isEmpty()) {
            percentage = sum / marks.size();
        }
    }

    @Override
    public String toString() {
        return subjects.toString();
    }

    @Override
    public int compareTo(Student o) {
        return -1;
    }
}

Main Method :

import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;

public class NewClass {

    public static void main(String[] args) {
        Set<String> sub = new HashSet<>();
        sub.add("Maths");
        sub.add("Science");
        sub.add("English");
        List<Integer> marks1 = new LinkedList<Integer>();
        marks1.add(45);
        marks1.add(36);
        marks1.add(98);
        Student student1 = new Student(1, "Payal", sub, marks1);
        sub.add("History");
        System.out.println(student1);
    }
}

1 : Reason why other code is not working is they make collection unmodifiableSet and unmodifiableList but to the local object while we just need to create new object instead of pointing old reference.
2 : And Second prevent modification of return value, for that just make instance variable to final or you can create new collection object and return it, but if you do that then it create new object each time you call getXXXX method while actually you don't need that object.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM