如果我们记录它的不变性,我们可以将对象视为不可变的

时间:2016-01-22 12:47:27

标签: java multithreading immutability

例如,我有这个:

public class Container{

    private final List<String> strs;

    /**
    * Contructs {@code Container} by a given {@code List}
    * The content of the list referenced should not be modified 
    * after the constructor invocation.
    */
    public Container(List<String> strs){
        this.strs = strs;
    }
    //Other staff
}

Container的状态可以在构建后修改,但我们在文档中禁止。该对象可以被视为不可变的吗?在构造函数中进行复制并不是完全可取的。

不变性是针对线程安全的。

5 个答案:

答案 0 :(得分:3)

如果您不想复制列表,但仍希望它真正不可变,则可以使用persistent data structure之类的Clojure

在Clojure中,所有值都是不可变的,因此您可以随时安全地传递它们。当有人将一个项目添加到列表的前面时,这会逻辑上创建一个新列表,但实际上它只添加一个指向旧列表头部的元素,而不必复制所有以前的列表。 Clojure中的所有数据结构都是这样的,您也可以在Java中使用它们(另请参阅what's a good persistent collections framework for use in java?)。

或者,您可以使用pcollections

答案 1 :(得分:2)

通常,您不应将类的内部结构暴露给外部。这对于imutables来说非常重要。

因此,我建议您在构造函数中使用new ArrayList(x)制作副本。

此外,您可以使用Collections.unmodifiableList()来防止来自班级内部的修改。

我建议两者结合,看起来像这样:

import java.util.Collections;
...

public Container(List<String> strs){
    this.strs = Collections.unmodifiableList(new ArrayList<>(strs));
}

您的容器将在调用构造函数时记住列表的成员。如果列表在其他地方被更改,它将不会对您的imutable产生任何影响。

即使容器中的代码也无法修改列表 - 列表会抛出UnsupportedOperationException

完整的,有效的示例代码:

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

public class X {

    public static void main(String[] args) {

        // create a list
        List<String> myList = new ArrayList<>();
        myList.add("a");
        myList.add("b");

        // hand it over to the container
        Container container = new Container(myList);

        // modify it afterwards
        myList.add("BUH!");

        // check contents of container
        for (String item : container.strs) {
            System.out.println(item);
        }
    }

}

class Container{

    final List<String> strs;

    /**
    * Contructs {@code Container} by a given {@code List}
    * The content of the list referenced should not be modified 
    * after the constructor invokation.
    */
    public Container(List<String> strs){
        this.strs = Collections.unmodifiableList(new ArrayList<>(strs));
    }
    //Other staff
}

输出:

a
b

(不输出BUH!

答案 2 :(得分:2)

您可以记录它以提高性能,但我只建议使用非公共API的方法。在我的公司中,我们构建了一个框架,并设法取消一些这些保护以提高性能/减少内存使用,但仅限于内部代码。我们决定假设框架开发人员不会陷入困境并且运行良好。

如果您的API将会暴露给外面的其他开发人员,那么您应始终保持安全,特别是出于安全考虑。如果您真的需要提高性能/内存使用率,那么这是一个选项,但请谨慎应用。

答案 3 :(得分:2)

您决定允许这样的结构是保证正确性和性能之间的权衡。

构建器模式可能为您提供第三种处理方式(取决于它是否适用于您的列表构造):

构造Container创建一个Builder,它收集列表元素,然后最终调用(私有)Container构造函数。这样可以方便地收集列表元素,并避免在构造最终对象时复制列表:

public class Container {
     public static class Builder {
         public Builder add(String s) { strs.add(s); return this; }
         public Container create() { 
               Container c = new Container(Collections.unmodifiableList(strs)); 
               strs = null; 
               return c; 
         }
         private List<String> strs = new ArrayList<>();
     }

     private final List<String> strs;

     private Container(List<String> strs){
          this.strs = strs;
     }
}

答案 4 :(得分:1)

一般来说,没有。可以在线程之间安全地共享不可变对象。在线程之间共享的安全对象不仅仅是文档问题;它的类必须以特定的方式实现。说&#34;这个类的对象是不可变的&#34;没有让它变得不可变的实现会积极误导,不真实,应该被视为一个bug。