为什么不可变 class 不能有 setter 方法?

Why can't an immutable class have setter methods?

我正在阅读有关不可变 classes 的内容,据说使 class 不可变的方法是:

我认为第三个条件是不必要的。当我们将变量设置为 final 并为其提供任何值时,即使通过 setter 方法也无法为其分配新值(因为一旦为其分配了值,便无法更改 final 变量) .那么为什么我们需要没有setter方法的第三个条件呢?

我是不是理解错了?

你说的有点对。根据定义,setter 用给定值替换字段。如果所有字段都是最终字段,那么您无论如何都不可能提供 setter。

我对如何编写不可变 class 的描述是:

  • 使所有字段最终
  • 确保每个字段的类型本身是不可变的

如果您非常小心确保它们永远不会更改,则可以使用可变字段编写不可变 class,但在这种情况下您需要非常小心。

是的,可以减少到

  1. 将 class 设置为 final 以防止继承;
  2. 使可变变量成为最终变量,因此不必费心提供任何设置器;

然而,出于教育目的,较短的要点可能效果更好 - 即使有点多余。

public class Person{
  private String name;
  public Person(String name){
    this.name = name;
  }
  public String getName(){
    return this.name;
  }
  public void setName(String name) {
    this.name = name;
  }
}

现在,Person 显然不是不可变的 class。这并不意味着,Person 的一个实例不能是另一个 class 的成员(据推测)是不可变的。

public final class MyImmutableClass {
  // p is final, so it can't be re-referenced
  private final Person p;

  public MyImmutableClass(Person p) {
    this.p = p;
  }
  // it can be altered, though
  public void setPersonName(String name) {
    this.p.setName(name);
  }
  public String toString() {
    return "Person: " + p.getName();
  }
}

现在,我们有一个不可变的 class,但是,它确实包含一个 setter。这 setter 主动更改(最终)字段 p.

的成员
public static void main(String[] args) {
  MyImmutableClass c = new MyImmutableClass(new Person("OriginalName"));
  System.out.println(c);
  c.setPersonName("AlteredName");
  System.out.println(c);
}

还有 .. 给你了。通过 setter 更改成员(即使变量是最终变量)。请理解,'final variable' 不一定是常量,在大多数情况下它的状态可以改变。 final 变量的要点在于它不能是 re-referenced。注意,我们也可以有这样的方法:

public void setPerson(Person p) {
  this.p.setName(p.getName());
}

final 变量本身只是一个常量,以防类型本身是不可变类型,或者如果它是原始类型,但您应该了解大多数类型都是可变的。 类型是不可变的还是原始类型并且声明为最终类型?当然,添加一个setter。但是,为了什么目的?误导使用你的人class?

第 2 项中提到的变量可以是一个引用,它可以是可变的(如列表或集合),即使变量本身是 final.

这就是为什么我们有像 Collections.unmodifiableList 这样的实用程序来使可变 类 几乎不可变。

所以禁止设置器是为了防止意外更改最终变量的状态。

将字段设置为 final 不足以(甚至不需要)保证不变性。您需要在不可变 class.

中制作可变对象的防御副本
class Foo {
   private String str;
   public Foo(String str) {
      this.str = str;
   }

   public String getString() {
       return str;
   }
}

上面的 class 是不可变的,因为:

  • 字符串是不可变的。
  • 字段 'str' 是私有的,不能更改。

现考虑以下MyDateclass.


public class MyDate {
    private Date; // date is not immutable
    public  MyDate(Date date) {
        this.date = date;
    }

    public Date getDate() {
        return date;
    }
}

上面的 MyDate class 不是不可变的,因为用户可以执行以下操作:

Date d = new Date(<someDate>);
MyDate md = new MyDate(d);
d.set(<someDate>);  // oops, just changed value in MyDate via external reference.

也可以通过 getDate() 完成同样的操作。

要使 MyClass 不可变,请在构造函数中制作 Date 和 getter 的防御性副本。这些可以防止 class 的用户更改 date 字段:

  • 使用对构造函数参数的引用。
  • 通过 getter
  • 检索日期字段
public class MyDate {
    private Date; // date is not immutable
    public  MyDate(Date date) {
        this.date = new Date(date);
    }

    public Date getDate() {
        return new Date(date);
    }
}