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

Sza*_*ego 19 java field class immutability

我有一个Employees像这样调用的不可变类:

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

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

    public List<Person> getPersons() {
        return persons;
    }
}
Run Code Online (Sandbox Code Playgroud)

如何让这个类保持不变?

我做了现场private,并final和我没有提供setter方法.这足以实现不变性吗?

Dav*_*INO 18

编辑的答案不仅解释了具有可变版本的情况Person,而且还具有不可变版本的情况Person.


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

Employees employees = new Employees();
employees.getPersons().add(new Person());
Run Code Online (Sandbox Code Playgroud)

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

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

  • Person 是不可改变的
  • Person 是可变的

场景1 - Person是不可变的

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

您还需要创建final类或至少方法getPersons以确保没有人提供该getPersons方法的可变覆盖版本.

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;
    }
}
Run Code Online (Sandbox Code Playgroud)

场景2 - Person是可变的

您需要方法中创建深层副本personsgetPersons.

您需要在构造函数上创建一个深层副本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;
    }
}
Run Code Online (Sandbox Code Playgroud)

这部分答案是为了说明为什么personsParameter传递给构造函数的深层副本可以创建可变版本Employees:

List<Person> personsParameter = new ArrayList<>();
Person person = new Person();
person.setName("Pippo");
personsParameter.add(person);
Employees employees = new Employees(personsParameter);

// Prints Pippo    
System.out.println(employees.getPersons().get(0).getName()); 


employees.getPersons().get(0).setName("newName");

// Prints again Pippo    
System.out.println(employees.getPersons().get(0).getName()); 

// But modifiyng something reachable from the parameter 
// used in the constructor 
person.setName("Pluto");

// Now it prints Pluto, so employees has changed    
System.out.println(employees.getPersons().get(0).getName()); 
Run Code Online (Sandbox Code Playgroud)

  • @DavideLorenzoMARINO在这种情况下,最好写一个复制ctor或`static`复制方法 - `clone()`有**大量**问题; 作为该方法的作者并没有解决这个问题,因为你仍然需要调用`super.clone()`并且这些问题仍然存在.为了使用`clone()`,我很想回答这个问题; 但决定反对.这个答案真正错过的是一个简单的解释 - "如果一个`类`它是不可变的,它的所有成员都是不可变的".因此,如果`Person`是不可变的,那么可以避免整个`clone()`废话. (2认同)

The*_*ind 17

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

你有2个选择:

  1. 创建一个防御性的副本,List并在调用get时返回它.
  2. 将您的列表包装为不可变/不可修改List并将其返回(或List用此替换原件,然后您可以安全地返回它而无需进一步包装)

注意:您必须确保Person是不可变或者为每个创建的防守副本PersonList

  • @Szanownego - 构建`List`之后.使用`persons = Collections.unmodifiableList(persons)`.在你的`getPersons`中返回`人` (4认同)

Mar*_*oun 8

答案可以在文档中找到 - 定义不可变对象的策略:

  1. 不要提供"setter"方法 - 修改字段引用的字段或对象的方法.

  2. 使所有字段成为最终和私有.

  3. 不允许子类重写方法.

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

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

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


Tob*_*tto 6

你可以做得更好

import java.util.Collections;
...
public List<Person> getPersons() {
    return Collections.unmodifiableList( persons);
}
Run Code Online (Sandbox Code Playgroud)

  • @DavideLorenzoMARINO - 但是这里无法阻止.不要在`Person`中提供setter. (2认同)