Reputation: 31
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;
}
}
Upvotes: 1
Views: 1901
Reputation: 79075
You can use JEP 395: Records feature, introduced as part of Java SE 16, to create an immutable class without requiring much ceremony.
If you have already gone through the above link, you must have figured out that you can do it simply as
record Tutor(String name, Set<Student> tutees) { }
What you get in turn are:
final class Tutor
.Tutor(String name, Set<Student> tutees)
.private final
fields, name
and tutees
and their corresponding public
accessor method with the same name and return type.equals
, hashCode
and toString
methods.Student.java
record Student(String name, String course) { }
Tutor.java
import java.util.Set;
record Tutor(String name, Set<Student> tutees) { }
Main.java
import java.util.Set;
class Main {
public static void main(String[] args) {
Set<Student> cscStudents = Set.of(
new Student("Harry", "Java-8"),
new Student("Tina", "Java-9"),
new Student("Andy", "Java-11")
);
Set<Student> scienceStudents = Set.of(
new Student("Tony", "Phy"),
new Student("Kerry", "Chem"),
new Student("John", "Bio")
);
Tutor t1 = new Tutor("Mark", cscStudents);
Tutor t2 = new Tutor("Robin", scienceStudents);
Tutor t3 = new Tutor("Mark", Set.of(
new Student("Andy", "Java-11"),
new Student("Harry", "Java-8"),
new Student("Tina", "Java-9")
)
);
System.out.println(t1);
System.out.println();
System.out.println(t1.tutees());
System.out.println();
System.out.println("Students of " + t1.name() + ":");
t1.tutees()
.stream()
.forEach( t -> System.out.println(t.name()) );
System.out.println();
System.out.println(t1.equals(t2));
System.out.println(t1.equals(t3));
}
}
Output:
Tutor[name=Mark, tutees=[Student[name=Andy, course=Java-11], Student[name=Harry, course=Java-8], Student[name=Tina, course=Java-9]]]
[Student[name=Andy, course=Java-11], Student[name=Harry, course=Java-8], Student[name=Tina, course=Java-9]]
Students of Mark:
Andy
Harry
Tina
false
true
Upvotes: 0
Reputation: 131346
The Tutor
class presents many aspects promoting its immutability :
Set<Student>
is protected against the modifications However, the defensive copy of the constructor is not complete.
It also has to copy the Student
s 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.
Upvotes: 4
Reputation: 41
Do you really need to return the Set of Students? If you really need that you can hide that by using an interface that provides only getters, something like
interface IStudent {
public String getName();
public String getCourse();
}
class Student : implements IStudent { ...}
and in your Tutor you return Set<IStudent>
Upvotes: 1
Reputation: 190
Proper way is to make an object immutable is to:
Upvotes: 2
Reputation: 1
To make the Tutor class immutable, you should use the "final" modifier on all the fields inside a Tutor, not on the Tutor's class definition.
Upvotes: 0