java-不可变对象的所有属性都必须是最终的吗?

不可变对象的所有属性都必须是final吗?

据我说不是。 但是我不知道我是否正确。

8个解决方案
52 votes

不可变对象(所有属性都是final)和有效不可变对象(属性不是final,但不能更改)之间的主要区别是安全发布。

由于Java内存模型为最终字段提供了保证,因此您可以在多线程上下文中安全地发布不可变对象,而不必担心添加同步。

final字段还允许程序员无需同步即可实现线程安全的不可变对象。 即使所有线程都使用数据竞争在线程之间传递对不可变对象的引用,线程安全的不可变对象也被所有线程视为不可变的。 这可以提供安全保证,以防止由于错误或恶意代码而滥用不可变类。 必须正确使用final字段以保证不变性。

附带说明,它还可以实现不可变性(如果由于忘记了它应该是不可变的而试图在类的将来版本中对那些字段进行突变,则不会编译)。


澄清说明

  • 将对象的所有字段都设为final不会使其不变-您还需要确保(i)其状态不变(例如,如果对象包含What???,则不能进行任何突变操作(添加,删除。)。 。)必须在施工后完成),并且(ii)在施工过程中您不要让i逃脱
  • 一旦安全发布了一个有效的不可变对象,它将是线程安全的
  • 不安全发布的示例:

    What???

    该程序理论上可以打印What???。如果i是最终决定,那将不是合法的结果。

assylias answered 2020-02-13T22:18:36Z
15 votes

您可以轻松地通过单独封装来保证不变性,因此没有必要:

// This is trivially immutable.
public class Foo {
    private String bar;
    public Foo(String bar) {
        this.bar = bar;
    }
    public String getBar() {
        return bar;
    }
}

但是,在某些情况下,您还必须通过封装来保证它,因此这还不够:

public class Womble {
    private final List<String> cabbages;
    public Womble(List<String> cabbages) {
        this.cabbages = cabbages;
    }
    public List<String> getCabbages() {
        return cabbages;
    }
}
// ...
Womble w = new Womble(...);
// This might count as mutation in your design. (Or it might not.)
w.getCabbages().add("cabbage"); 

这样做可以捕捉一些琐碎的错误并清楚地表明您的意图,这不是一个坏主意,但是“所有字段都是最终的”和“类是不可变的”并不是等效的语句。

millimoose answered 2020-02-13T22:19:05Z
6 votes

不可变=不可更改。 因此,使属性最终确定是一个好主意。 如果没有保护对象的所有属性不被更改,我不会说对象是不可变的。

但是如果对象的私有属性未提供任何设置方法,则该对象也是不可变的。

Kai answered 2020-02-13T22:19:30Z
5 votes

创建不可变对象后,不得对其进行任何修改。 当然,决赛有助于实现这一目标。 您保证不会更改它们。 但是,如果您的对象内部有一个最终数组,该怎么办? 当然,引用不能更改,但是元素可以更改。 在这里,我几乎也提出了相同的问题:

链接

Eugene answered 2020-02-13T22:19:54Z
5 votes

仅仅将一个对象声明为final并不会使其固有地不可变。 以此类为例:

import java.util.Date;

/**
* Planet is an immutable class, since there is no way to change
* its state after construction.
*/
public final class Planet {

  public Planet (double aMass, String aName, Date aDateOfDiscovery) {
     fMass = aMass;
     fName = aName;
     //make a private copy of aDateOfDiscovery
     //this is the only way to keep the fDateOfDiscovery
     //field private, and shields this class from any changes that 
     //the caller may make to the original aDateOfDiscovery object
     fDateOfDiscovery = new Date(aDateOfDiscovery.getTime());
  }

  /**
  * Returns a primitive value.
  *
  * The caller can do whatever they want with the return value, without 
  * affecting the internals of this class. Why? Because this is a primitive 
  * value. The caller sees its "own" double that simply has the
  * same value as fMass.
  */
  public double getMass() {
    return fMass;
  }

  /**
  * Returns an immutable object.
  *
  * The caller gets a direct reference to the internal field. But this is not 
  * dangerous, since String is immutable and cannot be changed.
  */
  public String getName() {
    return fName;
  }

//  /**
//  * Returns a mutable object - likely bad style.
//  *
//  * The caller gets a direct reference to the internal field. This is usually dangerous, 
//  * since the Date object state can be changed both by this class and its caller.
//  * That is, this class is no longer in complete control of fDate.
//  */
//  public Date getDateOfDiscovery() {
//    return fDateOfDiscovery;
//  }

  /**
  * Returns a mutable object - good style.
  * 
  * Returns a defensive copy of the field.
  * The caller of this method can do anything they want with the
  * returned Date object, without affecting the internals of this
  * class in any way. Why? Because they do not have a reference to 
  * fDate. Rather, they are playing with a second Date that initially has the 
  * same data as fDate.
  */
  public Date getDateOfDiscovery() {
    return new Date(fDateOfDiscovery.getTime());
  }

  // PRIVATE //

  /**
  * Final primitive data is always immutable.
  */
  private final double fMass;

  /**
  * An immutable object field. (String objects never change state.)
  */
  private final String fName;

  /**
  * A mutable object field. In this case, the state of this mutable field
  * is to be changed only by this class. (In other cases, it makes perfect
  * sense to allow the state of a field to be changed outside the native
  * class; this is the case when a field acts as a "pointer" to an object
  * created elsewhere.)
  */
  private final Date fDateOfDiscovery;
}
answered 2020-02-13T22:20:14Z
2 votes

没有。

例如,请参见java.lang.String的实现。字符串在Java中是不可变的,但是字段hash不是最终的(在第一次调用hashCode并随后对其进行缓存时,它是惰性计算的)。 但这是可行的,因为hash只能采用一个非默认值,该值在每次计算时都相同。

ZhekaKozlov answered 2020-02-13T22:20:39Z
2 votes

字符串类是不可变的,但属性哈希不是最终的

可以,但是有一些规则/限制,那就是每次我们访问可变属性/字段都必须提供相同的结果。

在String类中,实际上是在最终的字符数组上计算出的哈希码,如果构造了String,则哈希码不会改变。 因此,不可变类可以包含可变字段/属性,但是它必须确保每次访问字段/属性时都将产生相同的结果。

要回答您的问题,并非必须将所有字段都归为一成不变的类。

欲了解更多信息,请访问[blog]:[http://javaunturnedtopics.blogspot.in/2016/07/string-is-immutable-and-property-hash.html]

Tarun Bedi answered 2020-02-13T22:21:18Z
0 votes

不必要,您可以通过将成员设置为非最终成员但私有并将其修改(除非在构造函数中)来实现相同的功能。 不要为它们提供setter方法,如果它是一个可变对象,则不要泄漏该成员的任何引用。

请记住将参考变量定为final,仅确保不会将其重新分配其他值,但您仍可以更改该参考变量所指向的对象的各个属性。 这是关键点之一。

amitkumar12788 answered 2020-02-13T22:21:43Z
translate from https://stackoverflow.com:/questions/16061030/must-all-properties-of-an-immutable-object-be-final