Reputation: 118
Currently studying for an exam... ran into this question on a past paper.
Consider the following Student and Tutor classes:
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;
}
}
Tutor:
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;
}
}
Re-write the Tutor class to make it immutable(without modifying the Student class).
I know that the name field should have the final
modifier to ensure thread safety.
If the tutees Set
contains mutable Student Objects and the Student class cannot be changed then how do we make the class immutable? Perhaps creating a clone of the Set and upon every call to the getTutees
method clear tutees
and add the cloned elements to it?
Or is it already immutable despite the collection containing mutable objects?
Upvotes: 3
Views: 1521
Reputation: 96394
The members of the collection also need to be immutable. Unmodifiable only means that no elements can be added or removed.
Even if the collection is unmodifiable, once code accessing it has a reference to an element belonging to that collection, then if that element is not immutable then it can be changed.
If the getter for the collection returns a defensive copy, making a new Set referencing new Student objects and returning that instead of the original set, then that will make the collection immutable.
public Set<Student> getTutees() {
Set newSet = new HashSet();
for (Student tutee : tutees) {
newSet.add(new Student(tutee.getName(), tutee.getCourse()));
}
return newSet;
}
Adding an example here to show that using Collections.unmodifiableSet
is not sufficient for this:
import java.util.*;
public class ImmutabilityExample {
public static void main(String[] args) {
Student student = new Student("Joe", "Underwater Basketweaving 101");
Tutor tutor = new Tutor("Bill", new Student[] {student});
Set<Student> students = tutor.getTutees();
System.out.println("before=" + students);
students.iterator().next().setName("Mary");
System.out.println("" + tutor.getTutees());
}
}
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 String toString() {
return "Student, name=" + name + ", course=" + course;
}
}
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;
}
}
The output for this is:
before=[Student, name=Joe, course=Underwater Basketweaving 101]
[Student, name=Mary, course=Underwater Basketweaving 101]
Upvotes: 3
Reputation: 8803
I know I am getting too deeper into what it seems to be not more than a homework question, but...
Premises:
Tutor
contains instances of Student
.Tutor
to be immutable. Thus, it implies that the contained instances of Student
must be immutable too.Student
immutable.So, what are our chances?
Tutor
return shallow copies of the Student
instancies. However, this implies that the system will offer a strange, unexpected behaviour to the client. Imagine this code:Tutor tutor=new Tutor(...); Set students=tutor.getTutees(); Student student=students.iterator().next(); student.setCourse("1"); ... Set students=tutor.getTutees(); Student student=students.iterator().next(); if (!student.getCourse().equals("1")) { throw new RuntimeException("What the hell??? I already set it before!!!"); }
IMHO, the immutability is not worth the confussion we induce the client code to.
class MyStudent extends Student { public MyStudent(...) { super(...); } @Override public void setCourse(String s) { // Prevent modification silently. } @Override public void setName(String s) { // Even worse: Prevent modification through runtime exceptions: throw new IllegalStateException(); } }
Same objection as before: The client code would still compile OK, but the behaviour would be strange and unexpected according to the standard javabean conventions.
My conclusion: Immutability is a design issue (not a programming issue), because if a class is immutable, it should have no setters at all.
So, altough the problem has some technically valid solutions, it is unsolvable in terms of good practices.
Upvotes: 2