简体   繁体   English

如何在java中使对象不可变

[英]How to make object immutable in java

As this is a hot topic these days, I fail to understand certain concept. 由于这是当前的一个热门话题,我无法理解某些概念。 Please excuse me if I sound stupid but when I tried creating immutable object most of the posts I found following points 请原谅我,如果我听起来很愚蠢,但当我尝试创建不可变对象的大部分帖子时,我发现了以下几点

  • Make class final - makes sense 让课堂决赛 - 有意义
  • Dont allow mutators (setters) for the attributes - makes sense 不要让mutators(setter)用于属性 - 这是有道理的
  • Make attributes private - makes sense 将属性设为私有 - 有意义

Now I fail to understand why we need below points 现在我无法理解为什么我们需要以下几点

  • Make constructor private and provide createInstance method with the same attributes as constructor or factory method ? 使构造函数私有,并提供与构造函数或工厂方法相同属性的createInstance方法? How does it help ? 它有什么用?
  • Make attributes final - post of the post fail to explain this point and some where I read to avoid the modification accidentally. 使属性最终 - 帖子的帖子无法解释这一点和我阅读的一些地方,以避免意外修改。 How can you modify accidentally, when there are no mutators and class is final ? 如果没有变异器并且课程是最终的,你怎么能意外修改? How making an attribute final is helping ? 如何让属性最终得到帮助?
  • Instead of factory pattern, can I use builder pattern ? 我可以使用构建器模式而不是工厂模式吗?

I am adding my class and test case here : 我在这里添加我的课程和测试用例:

    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;
    }
}

Test case 测试用例

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
        }

    }

This test case fails and prints cassandra. 此测试用例失败并打印cassandra。

Let me know if I am doing something wrong. 如果我做错了,请告诉我。

I'd start from making attributes final . 我从final制作属性开始。 Making attribute final guarantees that you cannot change the attribute value. 使属性final保证您不能更改属性值。 I think this is obvious. 我认为这是显而易见的。 (I will write additional comment to changing the content of references immutable objects later). (我将在以后更改引用不可变对象的内容时写下额外的注释)。

Now, when all your attributes are final they must be initiated via constructor. 现在,当所有属性都是final属性时,必须通过构造函数启动它们。 However some classes have a lot of attributes, so the constructor becomes huge. 但是有些类有很多属性,所以构造函数变得很大。 Moreover sometimes some attributes can be initialized to default values. 此外,有时可以将某些属性初始化为默认值。 Attempt to support this causes us to implement several constructors with almost random combination of arguments. 尝试支持这一点会导致我们使用几乎随机的参数组合来实现几个构造函数。 However Builder pattern helps us. 然而,Builder模式有助于我们。 But how to make user to use Builder instead of direct invocation of constructor? 但是如何让用户使用Builder而不是直接调用构造函数呢? The answer is making constructor private and creating static method that returns 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);
        } 
    }
}

And here is the typical usage pattern: 这是典型的使用模式:

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

As you can see builder pattern often is better than factory because it is much more flexible. 正如您所看到的那样,构建器模式通常比工厂更好,因为它更灵活。

I think that you missed one point in your question: references to other (mutable) objects. 我认为你在问题中错过了一点:对其他(可变)对象的引用。 If for example we add field Collection<Person> children to our Person class we have to care that getChildren() returns either Iterable or at least unmodifirable collection. 例如,如果我们在Person类中添加字段Collection<Person> children Person我们必须关心getChildren()返回Iterable或至少是不可修改的集合。

  • Make constructor private and provide createInstance method with the same attributes as constructor or factory method ? 使构造函数私有,并提供与构造函数或工厂方法相同属性的createInstance方法? How does it helps ? 它有什么用?

Answer : making the constructor private and providing createInstance() (factory method) does not help by itself: it is one of few things you should do in order to allow users to actually use the class and its instances while you still have the control of the way instances are created. 回答 :使构造函数createInstance()私有并提供createInstance() (工厂方法)本身并没有帮助:为了让用户在你仍然可以控制的情况下实际使用类及其实例,这是你应该做的一件事。实例的创建方式。

  • Make attributes final - the post fails to explain this point and somewhere I read to avoid the modification accidentally. 使属性最终 - 帖子无法解释这一点,我在某处读到以避免意外修改。 How can you modify accidentally, when there are no mutators and class is final ? 如果没有变异器并且课程是最终的,你怎么能意外修改? How making an attribute final is helping ? 如何让属性最终得到帮助?

Answer : declaring a class as final means that the user can't extend it, so it "blocks" the user from this kind of "workaround". :将类声明为final意味着用户无法扩展它,因此它会“阻止”用户使用这种“解决方法”。 Declaring an attribute as final won't allow the user of the class to change it. 将属性声明为final将不允许类的用户更改它。 It cannot be "modified accidentally", but it can be "modified viciously" using reflection. 它不能“意外修改”,但可以使用反射“恶意修改”。 Let's see an example, say you have: 让我们看一个例子,说你有:

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

from another class you can do as follows: 从另一个班级你可以做如下:

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
    }
}
  • Can instead of factory use builder pattern ? 可以代替工厂使用构建器模式吗?

Answer : you can use the builder pattern or any pattern that helps you control the creation of instances of the class. :您可以使用构建器模式或任何可帮助您控制类实例创建的模式。

Further: 进一步:
If you want to make sure your class is immutable, make sure that any getter returns a deep-copy of the class member. 如果要确保您的类是不可变的,请确保任何getter返回类成员的深层副本 This technique is called "protective/defensive copy". 这种技术被称为“保护/防御性复制”。 You can read more about it here 你可以在这里阅读更多相关信息

Making the constructor private and using the builder pattern are not necessary for immutability. 使构造函数成为私有的并使用构建器模式对于不可变性不是必需的。 However because your class can't provide setters and if it has many fields, using a constructor with many parameters can be detrimental to readability hence the idea to use the builder pattern (which needs a pervade constructor). 但是因为你的类不能提供setter并且它有很多字段,所以使用带有许多参数的构造函数可能对可读性有害,因此使用构建器模式(需要pervade构造函数)的想法。

The other answers seem to have missed an important point though. 其他答案似乎错过了重要的一点。

Using final fields is essential, not only to ensure that they don't get modified, but because otherwise you lose some important thread safety guarantees. 使用final字段是必不可少的,不仅要确保它们不被修改,还要因为否则会丢失一些重要的线程安全保证。 Indeed, one aspect of immutability is that it brings you thread safety. 实际上,不变性的一个方面是它为您带来了线程安全性。 If you don't make the fields final your class becomes effectively immutable . 如果你没有使字段成为最终,那么你的类就变得有效了 See for example Must all properties of an immutable object be final? 例如,参见必不可变对象的所有属性是否必须是最终的?

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM