创建不可变 class 时,集合是否应该只包含不可变对象?
When creating an immutable class, should collections only contain immutable objects?
目前正在备考...运行在过去的论文中解决这个问题。
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;
}
}
导师:
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).
我知道name字段应该有final
修饰符来保证线程安全
如果学生 Set
包含可变的学生对象,而学生 class 无法更改,那么我们如何使 class 不可变?也许创建 Set 的克隆并在每次调用 getTutees
方法时清除 tutees
并将克隆的元素添加到其中?
或者尽管集合包含可变对象,它是否已经不可变?
集合的成员也需要是不可变的。不可修改仅意味着不能添加或删除任何元素。
即使集合是不可修改的,一旦访问它的代码引用了属于该集合的元素,那么如果该元素不是不可变的,那么它就可以被更改。
如果集合 returns 的 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]
我知道我对似乎不过是家庭作业问题的问题太深入了,但是...
场所:
Tutor
包含 Student
. 的实例
- 我们希望
Tutor
是不可变的。因此,这意味着 Student
包含的实例也必须是不可变的。
- 我们不能让
Student
不可变。
那么,我们的机会有多大?
- 我们可以制作
Tutor
return 个 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!!!");
}
恕我直言,不可变性不值得我们对客户端代码造成混淆。
- 我们可以 扩展 学生并覆盖设置器以将其关闭:
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 约定,行为将是奇怪和意外的。
我的结论:不变性是一个设计问题(不是编程问题),因为如果class是不可变的,它应该没有完全是二传手.
因此,尽管该问题有一些技术上有效的解决方案,但就良好实践而言,它无法解决。
目前正在备考...运行在过去的论文中解决这个问题。
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;
}
}
导师:
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).
我知道name字段应该有final
修饰符来保证线程安全
如果学生 Set
包含可变的学生对象,而学生 class 无法更改,那么我们如何使 class 不可变?也许创建 Set 的克隆并在每次调用 getTutees
方法时清除 tutees
并将克隆的元素添加到其中?
或者尽管集合包含可变对象,它是否已经不可变?
集合的成员也需要是不可变的。不可修改仅意味着不能添加或删除任何元素。
即使集合是不可修改的,一旦访问它的代码引用了属于该集合的元素,那么如果该元素不是不可变的,那么它就可以被更改。
如果集合 returns 的 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]
我知道我对似乎不过是家庭作业问题的问题太深入了,但是...
场所:
Tutor
包含Student
. 的实例
- 我们希望
Tutor
是不可变的。因此,这意味着Student
包含的实例也必须是不可变的。 - 我们不能让
Student
不可变。
那么,我们的机会有多大?
- 我们可以制作
Tutor
return 个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!!!"); }
恕我直言,不可变性不值得我们对客户端代码造成混淆。
- 我们可以 扩展 学生并覆盖设置器以将其关闭:
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 约定,行为将是奇怪和意外的。
我的结论:不变性是一个设计问题(不是编程问题),因为如果class是不可变的,它应该没有完全是二传手.
因此,尽管该问题有一些技术上有效的解决方案,但就良好实践而言,它无法解决。