提问人:Kusterbeck 提问时间:5/26/2016 最后编辑:Kusterbeck 更新时间:5/26/2016 访问量:1384
创建不可变类时,集合是否应仅包含不可变对象?
When creating an immutable class, should collections only contain immutable objects?
问:
目前正在备考...在过去的论文中遇到了这个问题。
考虑以下学生和导师课程:
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;
}
}
重写 Tutor 类以使其不可变(不修改 Student 类)。
我知道名称字段应该有修饰符以确保线程安全。final
如果 tutees 包含可变的 Student 对象,并且 Student 类无法更改,那么我们如何使类不可变?也许创建 Set 的克隆,并在每次调用该方法时清除并将克隆的元素添加到其中?Set
getTutees
tutees
或者尽管集合包含可变对象,但它已经不可变了?
答:
3赞
Nathan Hughes
5/26/2016
#1
集合的成员也必须是不可变的。不可修改仅表示不能添加或删除任何元素。
即使集合是不可修改的,一旦访问它的代码引用了属于该集合的元素,那么如果该元素不是不可变的,那么它就可以被更改。
如果集合的 getter 返回一个防御性副本,创建一个引用新 Student 对象的新 Set 并返回该对象而不是原始集合,则这将使集合不可变。
public Set<Student> getTutees() {
Set newSet = new HashSet();
for (Student tutee : tutees) {
newSet.add(new Student(tutee.getName(), tutee.getCourse()));
}
return newSet;
}
在此处添加一个示例以表明使用是不够的:Collections.unmodifiableSet
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;
}
}
其输出为:
before=[Student, name=Joe, course=Underwater Basketweaving 101]
[Student, name=Mary, course=Underwater Basketweaving 101]
评论
2赞
VGR
5/26/2016
@AndrewTobilko 这将返回对相同 Student 对象的引用,允许直接修改它们,因此 Tutor 类实际上不是不可变的。
0赞
Andrew Tobilko
5/26/2016
@VGR,是的,对不起。我只想到了一个不可变的集合
0赞
Kusterbeck
5/26/2016
谢谢。这就是我的假设,我认为这就是问题所寻找的。只需为每个循环添加正确的内容即可:' for (Student tutee : tutees)'
0赞
Chill
5/26/2016
有一个名为 Collections.unmodifiableSet(set) 的好方法,可以将其放入不允许修改的集合中。
0赞
Kusterbeck
5/26/2016
@Chill 这会阻止修改集合(即无法添加或删除元素),但是您仍然可以修改集合中的可变元素。
2赞
Little Santi
5/26/2016
#2
我知道我太深入了,这似乎只不过是一个家庭作业问题,但是......
房屋:
Tutor
包含 的实例。Student
- 我们希望是不可变的。因此,它意味着包含的实例也必须是不可变的。
Tutor
Student
- 我们不能让它变得不可变。
Student
那么,我们的机会是什么?
- 我们可以返回实例的浅层副本。然而,这意味着系统将向客户提供一种奇怪的、意想不到的行为。想象一下这段代码:
Tutor
Student
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!!!"); }
恕我直言,不可变性不值得我们诱导客户端代码进行混淆。
- 我们可以扩展 Student 并覆盖 setter 以关闭它们:
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(); } }
与以前相同的反对意见:客户端代码仍然可以编译正常,但根据标准的 javabean 约定,行为将是奇怪和意外的。
我的结论是:不可变性是一个设计问题(而不是编程问题),因为如果一个类是不可变的,它就不应该有 setter。
因此,尽管该问题有一些技术上有效的解决方案,但就良好实践而言,它是无法解决的。
评论