简体   繁体   English

使用修饰符创建不可变类的好方法(线程安全)

[英]Good way to create a immutable class with modifiers (thread-safe)

I have a case when I want to avoid defensive copies, for data which might nevertheless be modified, but is usually simply read, and not written to. 我有一个案例,当我想避免防御性副本,对于可能仍然被修改的数据,但通常只是阅读,而不是写入。 So, I'd like to use immutable objects, with functional mutator methods, which is kind of usual (java lombok is able to do it more or less automatically). 所以,我想使用不可变对象,使用函数mutator方法,这是常见的(java lombok能够或多或少地自动执行)。 The way I proceed is the following: 我进行的方式如下:

public class Person {
    private String name, surname;
    public Person(String name, String surname) {....}
    // getters...

    // and instead of setters
    public Person withName(String name) {
       Person p= copy(); // create a copy of this...
       p.name= name;
       return p;           
    }

   public Person copy() {....}         
}

So, to get a copy of the person with a different name, I would call 因此,要获得具有不同名称的人的副本,我会打电话

p= new Person("Bar", "Alfred");
...
p= p.withName("Foo");

In practice, the objects are rather large (and I ended up using serialization to avoid the burden of writing the copy code). 在实践中,对象相当大(我最终使用序列化来避免编写复制代码的负担)。

Now, while browsing the web, I see a potential concurrency problem with this implementation, as my fields are not final, and thus, concurrent access might see the returned copy, for instance, without the new name change (as there is no warrantee on the order of operation in this context). 现在,在浏览网页时,我发现这个实现存在潜在的并发问题,因为我的字段不是最终的,因此,并发访问可能会看到返回的副本,例如,没有新的名称更改(因为没有保证在这种情况下的操作顺序)。

Of course, I can't make my fields final, with the current implementation, as I first do a copy, and then change the data in the copy. 当然,我无法使用当前实现将我的字段设为最终,因为我先复制,然后更改副本中的数据。

So, I'm looking for a good solution for this problem. 所以,我正在为这个问题寻找一个好的解决方案。

I might use volatile, but I feel it's not a good solution. 我可能会使用volatile,但我觉得这不是一个好的解决方案。

Another solution would be to use the builder pattern: 另一种解决方案是使用构建器模式:

class PersonBuilder {
   String name, surname; ....
}

public class Person {
   private final String name, surname;

   public Person(PersonBuilder builder) {...}

   private PersonBuilder getBuilder() {
      return new PersonBuilder(name, surname);
   }

  public Person withName(String name) {
     PersonBuilder b= getBuilder();
     b.setName(name);
     return new Person(b);
  }
}

Is there any problem here, and above all, is there a more elegant way of doing the same thing ? 这里有什么问题,最重要的是,是否有更优雅的方式做同样的事情?

I recommend you take a look at Guava's immutable collections , such as immutable list and how they create lists from builders etc. 我建议你看看Guava的不可变集合 ,例如不可变列表以及它们如何从构建器等创建列表。

The idiom is the following: 成语如下:

List<String> list1 = ImmutableList.of("a","b","c"); // factory method
List<String> list2 = ImmutableList.builder() // builder pattern
  .add("a")
  .add("b")
  .add("c")
  .build();

List<String> list3 = ...  // created by other means
List<String> immutableList3 = ImmutableList.copyOf(list3); // immutable copy, lazy if already immutable

I really like the idiom above. 我真的很喜欢上面的成语。 For an entity builder I would take the following approach: 对于实体构建器,我将采用以下方法:

Person johnWayne = Person.builder()
  .firstName("John")
  .lastName("Wayne")
  .dob("05-26-1907")
  .build();

Person johnWayneClone = johnWayne.copy() // returns a builder!
  .dob("06-25-2014")
  .build();

The builder here can be obtained from an existing instance via the copy() method or via a static method on the Person class (a private constructor is recommended) that return a person builder. 这里的构建器可以通过copy()方法从现有实例获得,也可以通过Person类(建议使用私有构造函数)上的静态方法获取,该方法返回person构建器。

Note that the above mimics a little Scala's case classes in that you can create a copy from an existing instance. 请注意,上面的内容模仿了Scala的一些案例类 ,因为您可以从现有实例创建副本。

Finally, don't forget to follow the guidelines for immutable classes : 最后,不要忘记遵循不可变类准则

  • make the class final or make all getters final (if the class can be extended); 使课程最终使所有的getter成为最终(如果该类可以扩展);
  • make all fields final and private; 使所有领域成为最终和私人;
  • initialize all fields in the constructor (which can be private if you provide a builder and/or factory methods); 初始化构造函数中的所有字段(如果您提供构建器和/或工厂方法,则可以是私有的);
  • make defensive copies from getters if returning mutable objects (mutable collections, dates, third party classes, etc.). 如果返回可变对象(可变集合,日期,第三方类等),则从getter制作防御性副本。

One possibility is to separate your interfaces surrounding such objects into an immutable variant (providing getters) and a mutable variant (providing getters and setters). 一种可能性是将围绕这些对象的接口分成不可变的变体(提供getter)和可变的变体(提供getter和setter)。

public interface Person {
   String getName();
}

public interface MutablePerson extends Person {
   void setName(String name);
}

It doesn't solve the mutability of the object per se but it does offer some guarantees that when you pass around the object using the immutable interface reference, you know that the code you're passing this to won't change your object. 它并没有解决对象本身的可变性,但它提供了一些保证,当你使用不可变接口引用传递对象时,你知道你传递给它的代码不会改变你的对象。 Obviously you need to control the references to the underlying object and determine the subset of functionality that has control of a reference via the mutable interface. 显然,您需要控制对底层对象的引用,并确定通过可变接口控制引用的功能子集。

It doesn't solve the underlying problem and I would favour immutable objects until I definitely need a mutable version. 它没有解决根本问题,我宁愿使用不可变对象,直到我肯定需要一个可变版本。 The builder approach works nicely, and you can integrate it within the object to give a modifier thus: 构建器方法运行良好,您可以将其集成到对象中以提供修饰符:

Person newPerson = existingPerson.withAge(30);

Why not make your fields final and your modifier methods directly create new objects? 为什么不让你的字段最终,你的修饰方法直接创建新对象?

public class Person {
    private final String name, surname;

    public Person(String name, String surname) {....}
    // getters...

    // and instead of setters
    public Person withName(String newName) {
       return new Person(newName, surname);         
    }

}

Your problem boils down to this: You want a method that safely publishes an effectively immutable, almost-but-not-quite-faithful copy of an effectively immutable object. 你的问题归结为:你想要一个方法, 安全地发布一个有效不可变的,几乎但不是非常忠实的有效不可变对象的副本。

I'd go with the builder solution: It's verbose as all get out, but Eclipse helps with that, and it allows all of the published objects to be actually immutable. 我会使用构建器解决方案:它很冗长,但是Eclipse会帮助它,并且它允许所有已发布的对象实际上是不可变的。 Actual immutability makes safe publication a no-brainer. 实际的不变性使得安全发布变得简单。

If I wrote it, it'd look like this: 如果我写了它,它看起来像这样:

class Person {
    public static final FooType DEFAULT_FOO = ...;
    public static final BarType DEFAULT_BAR = ...;
    public static final BazType DEFAULT_BAZ = ...;
    ...

    private final FooType foo;
    private final BarType bar;
    private final BazType baz;
    ...

    private Person(Builder builder) {
        this.foo = builder.foo;
        this.bar = builder.bar;
        this.baz = builder.baz;
        ...
    }

    public FooType getFoo() { return foo; }
    public BarType getBar() { return bar; }
    public BazType getBaz() { return baz; }
    ...

    public Person cloneWith(FooType foo) {
        return new Builder(this).setFoo(foo).build();
    }

    public Person cloneWith(BarType bar) {
        return new Builder(this).setBar(bar).build();
    }

    public Person cloneWith(FooType foo, BarType bar) {
        return new Builder(this).setFoo(foo).setBar(bar).build();
    }

    ...

    public class Builder{
        private FooType foo;
        private BarType bar;
        private BazType baz;
        ...

        public Builder() {
            foo = DEFAULT_FOO;
            bar = DEFAULT_BAR;
            baz = DEFAULT_BAZ;
            ...
        }

        public Builder(Person person) {
            foo = person.foo;
            bar = person.bar;
            baz = person.baz;
            ...
        }

        public Builder setFoo(FooType foo) {
            this.foo = foo;
            return this;
        }

        public Builder setBar(BarType bar) {
            this.bar = bar;
            return this;
        }

        public Builder setBaz(BazType baz) {
            this.baz = baz;
            return this;
        }

        ...

        public Person build() {
            return new Person(this);
        }
    }
}

Depends on how many fields you intend to change. 取决于您打算改变多少个字段。 You could make special Changed objects like: 您可以制作特殊的Changed对象,例如:

interface Person {
     public String getForeName();
     public String getSurName();
 }

class RealPerson implements Person {
    private final String foreName;
    private final String surName;

    public RealPerson (String foreName, String surName) {
        this.foreName = foreName;
        this.surName = surName;
    }

    @Override
    public String getForeName() {
        return foreName;
    }

    @Override
    public String getSurName() {
        return surName;
    }

    public Person setSurName (String surName) {
        return new PersonWithSurnameChanged(this, surName);
    }

}

class PersonWithSurnameChanged implements Person {
    final Person original;
    final String surName;

    public PersonWithSurnameChanged (Person original, String surName) {
        this.original = original;
        this.surName = surName;
    }

    @Override
    public String getForeName() {
        return original.getForeName();
    }

    @Override
    public String getSurName() {
        return surName;
    }
}

This may also mitigate the problem you have with cloning heavy objects. 这也可以缓解克隆重物的问题。

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

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