如何使用LinkedList作为实例字段管理不可变类?

时间:2016-10-07 08:39:16

标签: java class field immutability

我有一个名为Employees的不可变类,如下所示:

public final class Employees {
    private final List<Person> persons;

    public Employees() {
        persons = new LinkedList<Person>();
    }

    public List<Person> getPersons() {
        return persons;
    }
}

如何让这个类保持不变?

我创建了字段privatefinal,但我没有提供setter方法。这足以实现不变性吗?

6 个答案:

答案 0 :(得分:18)

编辑回答不仅解释了Person的可变版本的案例,而且还解释了Person的不可变版本。

你的课程是可变的,因为你可以这样做:

Employees employees = new Employees();
employees.getPersons().add(new Person());

请注意,如果您更改代码以创建不可变类,则不会将人员列表传递给构造函数,您将拥有一个非常有用的类,其中包含一个空的人员列表,因此我认为有必要传递{{ 1}}到构造函数。

现在有两种情况,具有不同的实现:

  • List<Person>是不可变的
  • Person是可变的

场景1 - Person是不可变的

您只需要在构造函数中创建一个不可变的persons参数副本

您还需要制作Person类或至少方法final ,以确保没有人提供getPersons方法的可变覆盖版本

getPersons

情景2 - public final class Employees { private final List<Persons> persons; public Employees(List<Person> persons) { persons = Collections.unmodifiableList(new ArrayList<>(persons)); } public List<Employees> getPersons() { return persons; } } 可变

您需要Person方法中创建persons的深层副本。

您需要在构造函数上创建getPersons的深层副本。

您还需要制作persons类或至少方法final ,以确保没有人提供getPersons方法的可变覆盖版本

getPersons

这部分答案是为了说明为什么传递给构造函数的public final class Employees { private final List<Persons> persons; public Employees(List<Person> persons) { persons = new ArrayList<>(); for (Person person : persons) { persons.add(deepCopy(person)); // If clone is provided // and creates a deep copy of person } } public List<Employees> getPersons() { List<Person> temp = new ArrayList<>(); for (Person person : persons) { temp.add(deepCopy(person)); // If clone is provided // and creates a deep copy of person } return temp; } public Person deepCopy(Person person) { Person copy = new Person(); // Provide a deep copy of person ... return copy; } } 的非深层副本可以创建personsParameter的可变版本:

Employees

答案 1 :(得分:17)

不,这还不够,因为在java中,偶数引用是按值传递的。因此,如果您的List's引用转义(当它们调用get时会发生这种情况),那么您的类不再是不可变的

您有两个选择:

  1. 创建List的防御副本,并在调用get时将其返回。
  2. 将您的列表包装为不可变/不可修改List并将其返回(或用此替换原始List,然后您可以安全地返回此列表而无需进一步包装)
  3. 注意:您必须确保Person不可变或为Person

    中的每个List创建防御性副本

答案 2 :(得分:8)

答案可在文档中找到 - A Strategy for Defining Immutable Objects

  1. 不提供“setter”方法 - 修改字段引用的字段或对象的方法。

  2. 将所有字段设为最终字段并保密。

  3. 不允许子类覆盖方法。

  4. 如果实例字段包含对可变对象的引用,则不允许更改这些对象

    4.1。不要提供修改可变对象的方法。

    4.2。不要共享对可变对象的引用。永远不要存储对传递给构造函数的外部可变对象的引用;如有必要,创建副本并存储对副本的引用。同样,必要时创建内部可变对象的副本,以避免在方法中返回原始文件。

答案 3 :(得分:6)

你可以用

做得更好
import java.util.Collections;
...
public List<Person> getPersons() {
    return Collections.unmodifiableList( persons);
}

答案 4 :(得分:4)

在这种情况下,我赞成不公开List或Map。如果你传递了集合,你就是假设它必须存储为List而打破封装。

如果您没有公开列表,您可以避免需要包装或提供防御性副本。

public final class Employees {
    private final List<Person> persons;

    public Employees() {
        persons = new LinkedList<Person>();
    }

    public Person getPerson(int n) {
        return persons.get(n);
    }  

    public int getPersonCount() {
        return persons.size();
    }
}

你想要避免防御性副本的原因是它们可以被称为很多。有人可以很容易地写出来(你会惊讶地发现这是经常写的)

for (int i = 0; i < employees.getPersons().size(); i++) {
    Person p = employees.getPersons().get(i);

}

通过提供特定方法,您可以优化该用例的代码。

答案 5 :(得分:0)

我认为这种方法的问题是我仍然可以使用getPersons()。add(x)。为了不允许这样,你必须创建一个接收人员列表的构造函数,然后实例化你的内部最终列表,例如ImmutableList。 其余的代码对我来说似乎没问题。

编辑:

正如Davide Lorenzo MARINO指出的那样,这还不够。您还需要在getter中返回列表的深层副本,这样用户就无法通过getter返回的引用来修改内部列表。

通过执行此操作,您可以将普通的LinkedList作为您的itnernal List(私有final)。在构造函数中,您可以创建List的深层副本,并在getter上返回内部List的深层副本。