簡體   English   中英

不可變對象的所有屬性都必須是最終的嗎?

[英]Must all properties of an immutable object be final?

不可變對象必須所有屬性都是final的嗎?

我會說他們沒有。 但我不知道我是否正確。

不可變對象(所有屬性最終)和有效不可變對象(屬性不是最終但不能更改)之間的主要區別是安全發布。

您可以在多線程上下文中安全地發布不可變對象,而不必擔心添加同步,這要歸功於Java 內存模型為 final 字段提供的保證

final 字段還允許程序員在不同步的情況下實現線程安全的不可變對象。 線程安全的不可變對象被所有線程視為不可變的,即使使用數據競爭在線程之間傳遞對不可變對象的引用也是如此。 這可以提供安全保證,防止錯誤或惡意代碼濫用不可變類。 必須正確使用 final 字段以保證不變性。

作為旁注,它還可以強制執行不變性(如果您嘗試在未來版本的類中改變這些字段,因為您忘記了它應該是不可變的,它不會編譯)。


澄清

  • 將對象的所有字段設為 final 並不會使其不可變 - 您還需要確保 (i) 它的狀態不會改變(例如,如果對象包含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是最終的,那將不是法律結果。

您可以僅通過封裝輕松保證不變性,因此沒有必要

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

這樣做是為了捕捉一些微不足道的錯誤,並清楚地展示你的意圖,但“所有字段都是最終的”和“類是不可變的”不是等價的陳述。

不可變 = 不變。 所以使屬性最終是一個好主意。 如果不是一個對象的所有屬性都受到保護而不會被更改,我不會說該對象是不可變的。

但是,如果一個對象不為其私有屬性提供任何設置器,它也是不可變的。

不可變對象在創建后不得以任何方式修改。 final 當然有助於實現這一目標。 您保證它們永遠不會改變。 但是如果你的對象中有一個最終的數組怎么辦? 當然,參考是不可變的,但元素是可變的。 看看我也給出的幾乎相同的問題:

關聯

簡單地將對象聲明為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;
}

不。

例如,查看java.lang.String的實現。 字符串在 Java 中是不可變的,但字段hash不是最終的(它是在第一次調用hashCode時延遲計算然后緩存的)。 但這是可行的,因為hash在每次計算時只能采用一個相同的非默認值。

字符串類是不可變的,但屬性哈希不是最終的

好吧,這是可能的,但有一些規則/限制,即訪問可變屬性/字段必須在我們每次訪問它時提供相同的結果。

在 String 類中,哈希碼實際上是根據最終的字符數組計算的,如果 String 已構造,則不會更改。 因此,不可變類可以包含可變字段/屬性,但它必須確保每次訪問字段/屬性時都會產生相同的結果。

要回答您的問題,不必將所有字段都放在不可變類中。

如需進一步閱讀,請訪問此處 [博客]:http: //javaunturnedtopics.blogspot.in/2016/07/string-is-immutable-and-property-hash.html

沒有必要,您可以通過將成員設為非最終但私有並且除了在構造函數中之外不修改它們來實現相同的功能。 不要為他們提供 setter 方法,如果它是一個可變對象,那么永遠不要泄漏該成員的任何引用。

請記住,將引用變量設置為 final,僅確保不會重新為其分配不同的值,但您仍然可以更改該引用變量指向的對象的各個屬性。 這是關鍵點之一。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM