在创建不可变类时,集合是否只包含不可变对象?

时间:2016-05-25 20:03:00

标签: java collections immutability 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;
    }
}
  

重写Tutor类以使其不可变(不修改Student类)。

我知道名称字段应该有final修饰符以确保线程安全。

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

或者它是否已经是不可变的,尽管该集合包含可变对象?

2 个答案:

答案 0 :(得分:3)

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

即使集合是不可修改的,一旦访问它的代码具有对属于该集合的元素的引用,那么如果该元素不是不可变的,那么它可以被更改。

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

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]

答案 1 :(得分:2)

我知道我越来越深入了解它似乎不仅仅是一个家庭作业问题,而是......

物业:

  1. Tutor包含Student
  2. 的实例
  3. 我们希望Tutor是不可变的。因此,它意味着所包含的Student实例也必须是不可变的。
  4. 我们无法使Student永久不变。
  5. 那么,我们的机会是什么?

    1. 我们可以让Tutor返回Student个实例的浅层副本。但是,这意味着系统将向客户端提供奇怪的,意外的行为。想象一下这段代码:
    2.     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!!!");
          }
      
      恕我直言,不可变性不值得我们诱导客户代码的骚扰。

      1. 我们可以扩展学生并覆盖设置者以关闭它们:
      2.     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

        所以,尽管问题有一些技术上有效的解决方案,但就良好做法来说,它是无法解决的