【问题标题】:Creating an immutable object without final fields?创建一个没有最终字段的不可变对象?
【发布时间】:2011-09-21 13:52:05
【问题描述】:

我们可以创建一个不可变对象而不使所有字段都为 final 吗?

如果可能的话,举几个例子会有所帮助。

【问题讨论】:

  • 使用final 只会帮助编译器捕获一些错误。一个类可以在 final 字段中是可变的,而在没有它们的情况下是不可变的。但是,如果你有一个不可变的类,它不能有 final 字段,你必须问为什么会这样...... ;)
  • 为什么不想使用final 字段?
  • @DavidCaunt 对字段的惰性评估,例如java.lang.String 的 hashCode。不进入决赛可能很有用,但要正确处理非常棘手。
  • 它使它不可变但不是线程安全的!!! docs.oracle.com/javase/specs/jls/se7/html/…

标签: java oop immutability final


【解决方案1】:

将所有字段声明为私有并仅定义 getter:

public final class Private{
    private int a;
    private int b;

    public int getA(){return this.a;}
    public int getB(){return this.b;}
}

引用@Jon Skeet 的评论,final 类修饰符可用于:

虽然 Private 的实例是不可变的,但 子类很可能是可变的。所以代码接收类型的引用 Private 不能依赖它是不可变的而不检查它是 只是 Private 的实例。

所以如果你想确保你引用的实例是不可变的,你也应该使用 final 类修饰符。

【讨论】:

  • 请注意,这个类不是最终的 - 因此,虽然 just Private 的实例是不可变的,但子类的实例很可能是可变的。因此,接收Private 类型引用的代码不能在不检查它是否只是Private 的实例的情况下依赖它是不可变的。
  • @Jon Skeet:是的,感谢您的指出。我的只是一个简单的答案......无论如何,final 是处理不应更改的字段的更准确方法。
  • @Heisenbug “因此,如果您还需要子类是不可变的,请使用 final 类修饰符。”如果使用final修饰符,则无法创建子类,请更正最后一行或让我理解
  • @Naroji:对不起,我的回答错了。我试图纠正它。 Jon 指出的是,如果你有一个对作为子类实例的对象的基类引用,你不能假设子类实例也是不可变的(所以你可以引用一些你认为不可变的东西,但实际上它不是) .如果您想确定一个类的不变性,请阻止它的子类化。
【解决方案2】:

是的,只是确保你的状态是私有的,并且你的类中没有任何东西会改变它:

public final class Foo
{
    private int x;

    public Foo(int x)
    {
        this.x = x;
    }

    public int getX()
    {
        return x;
    }
}

没有办法改变这个类中的状态,因为它是最终的,所以你知道没有子类会添加可变状态。

但是:

  • 非最终字段的分配与最终字段的内存可见性规则不同,因此可能可以观察到来自不同线程的对象“变化”。有关最终字段保证的更多详细信息,请参阅section 17.5 of the JLS
  • 如果您不打算更改字段值,我个人会最终记录该决定,以避免以后意外添加变异方法
  • 我不记得 JVM 是否通过反射防止改变 final 字段;显然,任何具有足够权限的调用者可以在上述代码中使x 字段可访问,并通过反射对其进行变异。 (根据 cmets 可以使用 final 字段,但结果可能无法预测。)

【讨论】:

  • 那么反射机制呢?
  • 对我来说有时很难理解你的回答速度。可能有两种可能性,或者你生活在某个平行世界,时间移动得更慢,或者你的手指有神一样的速度。 ;-)。
  • @jon Skeet:“非最终字段的分配与最终字段的内存可见性规则不同,因此可以观察到来自不同线程的对象“变化” ”。你能把它扩大一点吗?
  • @Heisenbug:基本上,构造函数内对 final 字段的任何赋值都保证在构造函数完成后对所有线程可见;非最终字段没有相同的保证。有关详细信息,请参阅 JLS 的第 17.5 节。
  • @JonSkeet:感谢您的解释:我可以编辑我的答案,将您的评论添加到最终班级声明中吗?我认为突出显示它可能很有用。
【解决方案3】:

术语“不可变”在用于描述 Java 对象时,应该表示线程安全的不变性。如果一个对象是不可变的,那么一般认为任何线程都必须遵守相同的状态。

单线程不变性并不是很有趣。如果真的是这个意思,应该是完全限定为“单线程”;更好的术语是“不可修改”。

问题是对“不可变”一词的这种严格用法给出官方参考。我不能;它基于 Java 大人物如何使用该术语。每当他们说“不可变对象”时,他们总是在谈论线程安全的不可变对象。

实现不可变对象的惯用方式是使用final 字段; final 语义经过专门升级以支持不可变对象。这是一个非常有力的保证;事实上,final 字段是唯一的方法; volatile 字段甚至 synchronized 块都无法阻止在构造函数完成之前发布对象引用。

【讨论】:

  • 虽然我不会认为一个对象是“不可变的”,除非它是线程安全的,除非它保证在引用暴露给外界后不会改变任何可见状态,但确实如此并不意味着具有内部可变状态的对象不应被视为“不可变”。一个不可变对象以对外部世界不可见的方式改变其内部状态通常是可以的,只要两个线程努力改变内部状态的同一方面的唯一结果是执行冗余工作(如字符串的哈希码)。
【解决方案4】:

是的,如果你创建了一个只包含私有成员并且没有提供 setter 的对象,那么它将是不可变的。

【讨论】:

    【解决方案5】:

    如果一个类不提供任何可以从外部访问来修改对象状态的方法,那么它就是不可变的。所以是的,您可以创建一个不可变的类,而无需使字段成为最终的。示例:

    public final class Example {
        private int value;
    
        public Example(int value) {
            this.value = value;
        }
    
        public int getValue() {
            return value;
        }
    }
    

    但是,在实际程序中没有必要这样做,如果您的类应该是不可变的,建议始终将字段设为 final。

    【讨论】:

      【解决方案6】:

      我相信答案是肯定的。

      考虑以下对象:

      public class point{
         private int x; 
         private int y;
         public point(int x, int y)
         {
            this.x =x; 
            this.y =y;
          }
      
         public int getX()
          {
             return x;
          }
      
          public int getY()
          { 
              return y;
          }
      
      }
      

      这个对象是不可变的。

      【讨论】:

        【解决方案7】:

        是的。将字段设为私有。不要在构造函数以外的任何方法中更改它们。当然,既然如此,为什么不将它们标记为最终?

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2015-08-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多