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

2022-09-02 11:30:39

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

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

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

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

如何使此类保持不可变?

我制作了字段和,但我没有提供 setter 方法。这是否足以实现不可变性?privatefinal


答案 1

已编辑的 Answer 不仅用 的可变版本解释了这种情况,而且还用 的不可变版本解释了 这种情况。PersonPerson


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

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

请注意,如果您更改代码以创建不可变类,则不会将人员列表传递给构造函数,您将有一个无用的类,其中包含一个永远空的人员列表,因此我假设这是将人员传递给构造函数所必需的。List<Person>

现在有两种方案,具有不同的实现:

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

方案 1 - 人员是不可变的

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

您还需要使最终类或至少方法getPersons,以确保没有人提供该方法的可变覆盖版本。getPersons

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

    public Employees(List<Person> persons) {
        persons =  Collections.unmodifiableList(new ArrayList<>(persons));
    }

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

方案 2 - 人员是可变的

您需要在 getPersons 方法中创建人员的深层副本

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

您还需要使最终类或至少方法getPersons,以确保没有人提供该方法的可变覆盖版本。getPersons

public final class Employees {
    private final List<Person> 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<Person> 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;
    }
}

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

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()); 

答案 2

不,这还不够,因为在java中,甚至引用也是按值传递的。因此,如果你的引用转义(当他们调用时会发生),那么你的类不再是不可变的List'sget

您有2个选择:

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

注意:您必须确保 它是不可变的,或者为PersonPersonList