Java 创建不可变类时,集合是否应仅包含不可变对象?

Java 创建不可变类时,集合是否应仅包含不可变对象?,java,collections,immutability,mutable,Java,Collections,Immutability,Mutable,目前正在准备考试。。。在过去的一篇论文中遇到了这个问题 考虑以下学生和导师课程: 导师: public final class Tutor { private String name; private final Set<Student> tutees; public Tutor(String name, Student[] students) { this.name = name; tutees = new HashSet&

目前正在准备考试。。。在过去的一篇论文中遇到了这个问题

考虑以下学生和导师课程:

导师:

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;
    }
}
公共期末班导师{
私有字符串名称;
私人期末辅导;
公共导师(字符串名称,学生[]学生){
this.name=名称;
tutees=新的HashSet();
for(int i=0;i
重新编写导师类,使其不可变(不修改学生类)

我知道name字段应该有
final
修饰符,以确保线程安全

如果tutees
Set
包含可变的学生对象,并且学生类无法更改,那么我们如何使类不可变?也许创建集合的克隆,每次调用
getTutees
方法时,清除
tutees
并将克隆的元素添加到其中


或者,尽管集合包含可变对象,它仍然是不可变的吗?

集合的成员也需要是不可变的。不可修改仅意味着不能添加或删除任何元素

即使集合是不可修改的,一旦访问它的代码引用了属于该集合的元素,那么如果该元素不是不可变的,则可以对其进行更改

如果集合的getter返回一个防御副本,创建一个引用新Student对象的新集合,并返回该集合而不是原始集合,那么这将使集合不可变

public Set<Student> getTutees() {
    Set newSet = new HashSet();
    for (Student tutee : tutees) {
        newSet.add(new Student(tutee.getName(), tutee.getCourse()));
    }
    return newSet;
}
其输出为:

before=[Student, name=Joe, course=Underwater Basketweaving 101]
[Student, name=Mary, course=Underwater Basketweaving 101]

我知道我对似乎不仅仅是一个家庭作业问题的理解太深了,但是

场所:

  • Tutor
    包含
    Student
    的实例
  • 我们希望
    Tutor
    是不可变的。因此,它意味着包含的
    Student
    实例也必须是不可变的
  • 我们不能使
    Student
    不变
  • 那么,我们的机会有多大

  • 我们可以制作
    Tutor
    返回
    Student
    实例的浅拷贝。然而,这意味着系统将向客户端提供一种奇怪的、意外的行为。想象一下这个代码:
  • 导师=新导师(…); Set students=tutor.getTutees(); Student=students.iterator().next(); 学生课程(“1”); ... Set students=tutor.getTutees(); Student=students.iterator().next(); 如果(!student.getCourse().等于(“1”)) { 抛出新的RuntimeException(“见鬼,我之前已经设置好了!!!”; } 总之,不变性不值得我们对客户机代码的混淆

  • 我们可以扩展Student并覆盖setters以关闭它们:
  • 班级MyStudent扩展了学生 { 公共MyStudent(…) { 超级(…); } @凌驾 公共void setCourse(字符串s) { //无声地防止修改。 } @凌驾 公共void集合名(字符串s) { //更糟糕的是:防止通过运行时异常进行修改: 抛出新的非法状态异常(); } } 与之前相同的反对意见:客户机代码仍然可以编译,但根据标准javabean约定,这种行为将是奇怪和意外的

    我的结论是:不变性是一个设计问题(而不是编程问题),因为如果一个类是不可变的,那么它就不应该有任何setter


    因此,尽管这个问题有一些技术上有效的解决方案,但就良好实践而言,它是无法解决的。

    有许多不可变的定义,因此只有测试的作者知道它们的含义。也许他们不关心包含的对象。或者,当您返回不可修改的集合时,他们可能希望您创建学生的副本。@AndrewTobilko将返回对相同学生对象的引用,允许直接修改这些对象,因此导师类实际上不会是不变的。@VGR,是的,抱歉。我只想到一个永恒的收藏谢谢。这就是我的假设,我想这就是问题所要寻找的。只需为每个循环添加正确的方法是:`for(Student tutee:tutees)`有一个很好的方法叫做Collections.unmodifiableSet(set),它将把它放在一个不允许修改的集合中。@Chill可以停止对集合的修改(即不能添加或删除元素),但是您仍然可以修改集合中的可变元素。
    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]
    
    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!!!"); } 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(); } }