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

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/16061030/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-10-31 21:46:12  来源:igfitidea点击:

Must all properties of an immutable object be final?

javaimmutabilityfinaljava-memory-model

提问by DRastislav

Must immutable objects have all properties be final?

不可变对象必须具有所有属性final吗?

According to me not. But I don't know, whether I am right.

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

回答by assylias

The main difference between an immutable object (all properties final) and an effectively immutable object (properties aren't final but can't be changed) is safe publication.

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

You can safely publish an immutable object in a multi threaded context without having to worry about adding synchronization, thanks to the guarantees provided by the Java Memory Model for final fields:

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

final fields also allow programmers to implement thread-safe immutable objects without synchronization. A thread-safe immutable object is seen as immutable by all threads, even if a data race is used to pass references to the immutable object between threads. This can provide safety guarantees against misuse of an immutable class by incorrect or malicious code. final fields must be used correctly to provide a guarantee of immutability.

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

As a side note, it also enables to enforce immutability (if you try to mutate those fields in a future version of your class because you have forgotten it should be immutable, it won't compile).

作为旁注,它还可以强制执行不变性(如果您尝试在类的未来版本中改变这些字段,因为您忘记了它应该是不可变的,它不会编译)。



Clarifications

澄清

  • Making all the fields of an object final does not make it immutable - you also need to make sure that (i) its state can't change (for example, if the object contains a final List, no mutating operations (add, remove...) must be done after construction) and (ii) you don't let thisescape during construction
  • An effectively immutable object is thread safe once it has been safely published
  • Example of unsafe publication:

    class EffectivelyImmutable {
        static EffectivelyImmutable unsafe;
        private int i;
        public EffectivelyImmutable (int i) { this.i = i; }
        public int get() { return i; }
    }
    
    // in some thread
    EffectivelyImmutable.unsafe = new EffectivelyImmutable(1);
    
    //in some other thread
    if (EffectivelyImmutable.unsafe != null
        && EffectivelyImmutable.unsafe.get() != 1)
        System.out.println("What???");
    

    This program could in theory print What???. If iwere final, that would not be a legal outcome.

  • 将对象的所有字段设为 final 并不会使其不可变 - 您还需要确保 (i) 其状态不会改变(例如,如果对象包含 a final List,则没有变异操作(添加、删除... ) 必须在施工后完成) 和 (ii)this在施工过程中不要让逃跑
  • 一个有效的不可变对象一旦被安全发布就是线程安全的
  • 不安全发布示例:

    class EffectivelyImmutable {
        static EffectivelyImmutable unsafe;
        private int i;
        public EffectivelyImmutable (int i) { this.i = i; }
        public int get() { return i; }
    }
    
    // in some thread
    EffectivelyImmutable.unsafe = new EffectivelyImmutable(1);
    
    //in some other thread
    if (EffectivelyImmutable.unsafe != null
        && EffectivelyImmutable.unsafe.get() != 1)
        System.out.println("What???");
    

    这个程序理论上可以打印What???。如果i是最终结果,那将不是法律结果。

回答by millimoose

You can easily guarantee immutability by encapsulation alone, so it's not necessary:

您可以仅通过封装轻松保证不变性,因此没有必要

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

However, you also mustguarantee it by encapsulation in some cases, so it's not sufficient:

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

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"); 

It's not a bad idea to do so to catch some trivial errors, and to demonstrate your intent clearly, but "all fields are final" and "the class is immutable" are not equivalent statements.

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

回答by Kai

Immutable = not changeable. So making properties final is a good idea. If not all properties of an object are protected from being changed I wouldn't say the object is immutable.

不可变 = 不可更改。所以使属性最终是一个好主意。如果不是对象的所有属性都受到保护不被更改,我不会说该对象是不可变的。

BUT an object is also immutable if it doesn't provide any setters for it's private properties.

但是,如果一个对象没有为其私有属性提供任何 setter,那么它也是不可变的。

回答by Eugene

Immutable objects MUST not be modified in any way after their creation. final of course helps to achieve that. You guarantee that they will not ever be changed. BUTwhat if you have an array inside your object that is final? Of course the reference is not changable, but the elements are. Look here at almost the same question I gave also:

不可变对象在创建后不得以任何方式修改。final 当然有助于实现这一目标。您保证它们永远不会改变。但是如果你的对象中有一个最终的数组怎么办?当然,引用是不可更改的,但元素是可更改的。看看这里,我也给出了几乎同样的问题:

Link

关联

回答by Eugene

Simply declaring an object as finaldoes not make it inherently immutable. Take for example this class:

简单地将对象声明为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;
}

回答by ZhekaKozlov

No.

不。

For example, see the implementation of java.lang.String. Strings are immutable in Java, but the field hashis not final(it is lazily computed the first time hashCodeis called and then cached). But this works because hashcan take on only one nondefault value that is the same every time it is computed.

例如,请参阅java.lang.String. 字符串在 Java 中是不可变的,但该字段hash不是最终的(它在第一次hashCode被调用时被延迟计算,然后被缓存)。但这是有效的,因为hash每次计算时只能采用一个相同的非默认值。

回答by Tarun Bedi

String class is Immutable but property hash is not final

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

Well it is possible but with some rules/restrictions and that is access to mutable properties/fields must provide same result every time we access it.

嗯,这是可能的,但有一些规则/限制,即访问可变属性/字段必须在每次访问时提供相同的结果。

In String class hashcode actually calculated on the final array of characters which is not going to change if String has constructed. Therefore immutable class can contain mutable fields/properties but it has to make sure that access to field/property will produce the same result every time it is accessed.

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

To answer your question it is not mandatory to have all the fields final in a immutable class.

要回答您的问题,不一定要将所有字段都放在不可变类中。

For further reading visit here [blog] : http://javaunturnedtopics.blogspot.in/2016/07/string-is-immutable-and-property-hash.html

如需进一步阅读,请访问此处 [博客]:http: //javaunturnedtopics.blogspot.in/2016/07/string-is-immutable-and-property-hash.html

回答by amitkumar12788

Not necessary, you can achieve same functionality by making member as non-final but private and not modifying them except in constructor. Don't provide setter method for them and if it is a mutable object, then don't ever leak any reference for that member.

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

Remember making a reference variable final, only ensures that it will not be reassigned a different value, but you can still change individual properties of an object, pointed by that reference variable. This is one of the key points.

记住将引用变量设为 final,只能确保它不会被重新分配一个不同的值,但您仍然可以更改由该引用变量指向的对象的各个属性。这是关键点之一。