Search code examples
javaimmutability

Java, Making a class Immutable


I came across this exercise online where I have two classes and I'm supposed to make the Tutor class immutable. However, the only thing I can think of is adding final to name field. When it comes to the constructor, I don't think I need to change the initialisation of the name variable as String is immutable. I'm not sure how to approach the collection and how to make this part of the constructor immutable. According to the exercise, I'm not supposed to change the Student class (which I can see is mutable)

public class Student {    
    private String name;
    private String course;

    public Student(String name, String course) {    
        this.name = name;
        this.course = course;    
    }

    public String getName() {
        return name;
    }

    public String getCourse() {
        return course;
    }

    public void setName(String name) {
        this.name = name;
    }

    public void setCourse(String course) {
        this.course = course;
    }
}

public final class Tutor {
    private String name;
    private final Set<Student> tutees;

    public Tutor(String name, Student[] students) {
        this.name = name;
        tutees = new HashSet<Student>();
        for (int i = 0; i < students.length; i++)
            tutees.add(students[i]);
    }

    public Set<Student> getTutees() {
        return Collections.unmodifiableSet(tutees);
    }

    public String getName() {
        return name;
    }
}

Solution

  • The Tutor class presents many aspects promoting its immutability :

    • the class is final
    • the Set<Student> is protected against the modifications
    • no method allowing to change directly the state of the class

    However, the defensive copy of the constructor is not complete.
    It also has to copy the Students elements of the array passed. Otherwise the client of the constructor may change any instance of them and make so the Tutor instance mutable such as :

    Student[] students = ...;
    Tutor tutor = new Tutor(name, students);
    students[0].setName("new Name!"); // break the immutability of Tutor
    

    You should write something like :

    public Tutor(String name, Student[] students){
      this.name = name;
      tutees = new HashSet<Student>();
      for (Student student : students){   
          Student copy = new Student(student.getName(), 
                                        student.getCourse());
          tutees.add(copy);
       }     
    }
    

    Additionally note that the Set returned by getTutees() is unmodifiable but elements contained in are as Student is mutable. So to make Tutor immutable you also have to create a copy of the Student elements as you return getTutees() such as :

    public Set<Student> getTutees(){
       Set<Student> students = new HashSet<>();
       for (Student student : tutees){
          Student copy = new Student(student.getName(), 
                                        student.getCourse());
          students.add(copy);
       }     
       return Collections.unmodifiableSet(students);
    }
    

    As you may notice, getting the immutability in these conditions (an instance that we wish immutable but that contains a collection referencing mutable instances) requires to write more code (to read/to maintain/to test) and to perform more processing (so slower to execute).
    If Student was an immutable class, the original getTutees() and the original constructor would be enough.