簡體   English   中英

如何在java中使對象不可變

[英]How to make object immutable in java

由於這是當前的一個熱門話題,我無法理解某些概念。 請原諒我,如果我聽起來很愚蠢,但當我嘗試創建不可變對象的大部分帖子時,我發現了以下幾點

  • 讓課堂決賽 - 有意義
  • 不要讓mutators(setter)用於屬性 - 這是有道理的
  • 將屬性設為私有 - 有意義

現在我無法理解為什么我們需要以下幾點

  • 使構造函數私有,並提供與構造函數或工廠方法相同屬性的createInstance方法? 它有什么用?
  • 使屬性最終 - 帖子的帖子無法解釋這一點和我閱讀的一些地方,以避免意外修改。 如果沒有變異器並且課程是最終的,你怎么能意外修改? 如何讓屬性最終得到幫助?
  • 我可以使用構建器模式而不是工廠模式嗎?

我在這里添加我的課程和測試用例:

    public final class ImmutableUser {
    private final UUID id;
    private final String firstName;
    private final String lastName;

    public ImmutableUser(UUID id, String firstName, String lastName) {
        super();
        this.id = id;
        this.firstName = firstName;
        this.lastName = lastName;
    }
    /**
     * @return the id
     */
    public UUID getId() {
        return id;
    }
    /**
     * @return the firstName
     */
    public String getFirstName() {
        return firstName;
    }
    /**
     * @return the lastName
     */
    public String getLastName() {
        return lastName;
    }
}

測試用例

public class ImmutableUserTest {

        @Test(expected = IllegalAccessException.class)
        public void reflectionFailure() throws NoSuchFieldException, SecurityException, IllegalArgumentException, IllegalAccessException {
            ImmutableUser user = new ImmutableUser(UUID.randomUUID(), "john", "liu");
            Field i =user.getClass().getDeclaredField("firstName");
            i.setAccessible(true);
            i.set(user, "cassandra");
            System.out.println("user " + user.getFirstName()); // prints cassandra
        }

    }

此測試用例失敗並打印cassandra。

如果我做錯了,請告訴我。

我從final制作屬性開始。 使屬性final保證您不能更改屬性值。 我認為這是顯而易見的。 (我將在以后更改引用不可變對象的內容時寫下額外的注釋)。

現在,當所有屬性都是final屬性時,必須通過構造函數啟動它們。 但是有些類有很多屬性,所以構造函數變得很大。 此外,有時可以將某些屬性初始化為默認值。 嘗試支持這一點會導致我們使用幾乎隨機的參數組合來實現幾個構造函數。 然而,Builder模式有助於我們。 但是如何讓用戶使用Builder而不是直接調用構造函數呢? 答案是使構造函數private並創建返回構建器的靜態方法:

public class Person {
    private final String firstName;
    private final String lastName;
    private final Person mother;
    private final Person father;

    private Person(String firstName, String lastName, Person mother, Person father) {
        // init the fields....
    }

    public static PersonBuilder builder() {
        return new PersonBuilder();
    }


    public static class PersonBuilder {
        // here fields are NOT final 
        private String firstName;
        private String lastName;
        private Person mother;
        private Person father;

        public PersonBuilder bornBy(Person mother) {
            this.mother = mother;
             return this;
        }

        public PersonBuilder conceivedBy(Person father) {
             this.father = father;
             return this;
        }

        public PersonBuilder named(String firstName) {
             this.firstName = firstName;
             return this;
        }

        public PersonBuilder fromFamily(String lastName) {
             this.lastName = lastName;
             return this;
        }

        Person build() {
              return new Person(name, lastName, mother, father);
        } 
    }
}

這是典型的使用模式:

Person adam = Person.builder().named("Adam").build(); // no mother, father, family
Person eve = Person.builder().named("Eve").build(); // no mother, father, family
Person cain = Person.builder().named("Cain").conerivedBy(adam).bornBy(eve); // this one has parents

正如您所看到的那樣,構建器模式通常比工廠更好,因為它更靈活。

我認為你在問題中錯過了一點:對其他(可變)對象的引用。 例如,如果我們在Person類中添加字段Collection<Person> children Person我們必須關心getChildren()返回Iterable或至少是不可修改的集合。

  • 使構造函數私有,並提供與構造函數或工廠方法相同屬性的createInstance方法? 它有什么用?

回答 :使構造函數createInstance()私有並提供createInstance() (工廠方法)本身並沒有幫助:為了讓用戶在你仍然可以控制的情況下實際使用類及其實例,這是你應該做的一件事。實例的創建方式。

  • 使屬性最終 - 帖子無法解釋這一點,我在某處讀到以避免意外修改。 如果沒有變異器並且課程是最終的,你怎么能意外修改? 如何讓屬性最終得到幫助?

:將類聲明為final意味着用戶無法擴展它,因此它會“阻止”用戶使用這種“解決方法”。 將屬性聲明為final將不允許類的用戶更改它。 它不能“意外修改”,但可以使用反射“惡意修改”。 讓我們看一個例子,說你有:

final public class SomeClass {
    final Integer i = 1;
}

從另一個班級你可以做如下:

class AnotherClass {

    public static void main (String[] args) throws Exception {

        SomeClass p = new SomeClass();
        Field i =p.getClass().getDeclaredField("i");
        i.setAccessible(true);
        i.set(p, 5);
        System.out.println("p.i = " + p.i); // prints 5
    }
}
  • 可以代替工廠使用構建器模式嗎?

:您可以使用構建器模式或任何可幫助您控制類實例創建的模式。

進一步:
如果要確保您的類是不可變的,請確保任何getter返回類成員的深層副本 這種技術被稱為“保護/防御性復制”。 你可以在這里閱讀更多相關信息

使構造函數成為私有的並使用構建器模式對於不可變性不是必需的。 但是因為你的類不能提供setter並且它有很多字段,所以使用帶有許多參數的構造函數可能對可讀性有害,因此使用構建器模式(需要pervade構造函數)的想法。

其他答案似乎錯過了重要的一點。

使用final字段是必不可少的,不僅要確保它們不被修改,還要因為否則會丟失一些重要的線程安全保證。 實際上,不變性的一個方面是它為您帶來了線程安全性。 如果你沒有使字段成為最終,那么你的類就變得有效了 例如,參見必不可變對象的所有屬性是否必須是最終的?

暫無
暫無

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

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