【问题标题】:Why can't an immutable class have setter methods? [duplicate]为什么不可变类不能有 setter 方法? [复制]
【发布时间】:2019-10-25 17:57:47
【问题描述】:

我正在阅读有关不可变类的信息,据说使类不可变的方法是:

  • 1 - 将类设为 final 以防止继承
  • 2 - 将可变变量设为 final
  • 3 - 不要提供setter 方法。

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

我理解错了吗?

【问题讨论】:

标签: java immutability setter


【解决方案1】:

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

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

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

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

【讨论】:

  • 我仍然说,通常的做法可以很容易地定义一个在正常使用下无法修改其内容的对象。这不需要“最终”资格。在我的书中,任何这样的对象都是“不可变的”,无论您可能对违反约定和常识的可疑代码做什么。 - 我仍然没有在脑海中看到一个类如何修改实例变量。如果您在谈论类变量,那么我的逻辑仍然成立。不要提供修改类变量的方法,并且该变量不需要在类 def 中标记为“final”
  • @Steve 定义一个无法直接操作数据的对象很容易,但要确保它不能被间接操作则更加困难。即使有效地将一个可变参数引入构造函数,也需要您深度复制整个事物。
  • 我不反对。所以'final'是一个可以使用的工具,这取决于你的实现。不向构造函数提供不复制的可变参数是另一回事。 - 并且您可以将传递给构造函数的不可变变量标记为“最终”以使其无法修改吗?我不这么认为。 “最终”只会说无法更新对象引用。它没有说明在可变对象中可以做什么,即使它的引用被标记为“final”。
  • @Steve final 从来不是必要的。这只是一个很好的约束,它使代码更容易推理。如果一个类有几个字段,都定义为最终的,所有明确的值类型,(整数,字符串...),那么我可以快速确定它是不可变的,甚至无需阅读文档。如果甚至一个不是最终的,我需要检查整个班级以确保它没有改变。因此,虽然类不可变不是要求,但任何好的不可变类实现都应该使用它(IMO)。
  • 我完全同意。事实上,我刚刚意识到我误读了 OP。它说的是“方法”而不是“方法”,如“这些是工具”,而不是“这是方法”。我是在回应后者,这不是一开始发生的事情。
【解决方案2】:

是的,可以简化为

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

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

【讨论】:

    【解决方案3】:
    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 不是 Immutable 类。这并不意味着,Person 的实例不能是(假定)不可变的另一个类的成员。

    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();
      }
    }
    

    现在,我们有一个不可变的类,但它确实包含一个 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);
    }
    

    还有.. 你有它。通过设置器更改成员(即使变量是最终的)。请理解,“最终变量”不一定是常数,在大多数情况下,它的状态可以更改。 final 变量的要点是它不能被重新引用。注意,我们也可以有这样的方法:

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

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

    【讨论】:

    • 您将final reference 的不可变值与该引用所指对象的可变性混淆了。
    • @user207421 不,我不知道。如果可以更改类的成员,则包含该成员的对象会发生变异。
    • 不,不是。它仍然指的是同一个对象。它自己的状态由它自己的非静态原语和引用变量的值组成。
    • @user207421 这只是解释了为什么 java 中的 final 并不意味着它也是“不可变的”,将其与 C++ 相比,在 C++ 中你有引用的常量(这大致是 final在 java 中),以及对象的 constness(在 java 中没有对应物)。
    • 这就是为什么在将其他对象添加到不可变类时应该进行防御性副本的原因。并且在获取这些对象时,它还应该返回一个副本,而不是在不可变类中维护的那个。
    【解决方案4】:

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

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

    所以禁止 setter 是为了防止意外改变最终变量的状态。

    【讨论】:

      【解决方案5】:

      将字段设为 final 不足以(甚至不需要)保证不变性。您需要在不可变类中制作可变对象的防御性副本。

      class Foo {
         private String str;
         public Foo(String str) {
            this.str = str;
         }
      
         public String getString() {
             return str;
         }
      }
      

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

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

      现在考虑下面的MyDate 类。

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

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

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

      同样可以通过getDate()完成。

      要使MyClass 不可变,请在构造函数和getter 中创建Date 的防御性副本。这些可以防止类的用户更改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);
          }
      }
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2014-12-20
        • 2017-11-02
        • 2014-05-15
        • 2019-12-02
        • 2011-03-12
        相关资源
        最近更新 更多