Подтвердить что ты не робот

Как управлять неизменяемым классом с помощью LinkedList в качестве поля экземпляра?

У меня есть неизменяемый класс, называемый Employees следующим образом:

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

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

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

Как я могу сохранить этот класс неизменным?

Я сделал поле private и final, и я не представил метод setter. Достаточно ли этого для достижения неизменности?

4b9b3361

Ответ 1

Ответ отредактирован, чтобы объяснить не только случай с изменяемой версией Person, но также с неизменяемой версией Person.


Ваш класс изменен, потому что вы можете это сделать:

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

Обратите внимание, что если вы не измените свой код для создания неизменяемого класса, вы не будете передавать список лиц конструктору, у вас будет не полезный класс, содержащий всегда пустой список лиц, поэтому я предполагаю, что необходимо передать List<Person> конструктору.

Теперь есть два сценария с различными реализациями:

  • Person является неизменным
  • Person изменен

Сценарий 1 - Person является неизменным

Вам нужно только создать неизменяемую копию параметра лиц в конструкторе.

Вам также нужно сделать 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;
    }
}

Сценарий 2 - 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:

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's выйдет (что произойдет, когда они вызовут get), тогда ваш класс перестанет быть неизменным.

У вас есть 2 варианта:

  • Создайте защитную копию вашего List и верните ее, когда вызывается get.
  • Оберните свой список как неизменяемый/невосстанавливаемый List и верните его (или замените исходный List на это, тогда вы можете вернуть это безопасно без дальнейшей его упаковки)

Примечание. Вы должны убедиться, что Person является неизменным или создает защитные копии для каждого Person в List

Ответ 3

Ответ находится в документации - Стратегия определения неизменяемых объектов:

  • Не предоставляйте методы setter - методы, которые изменяют поля или объекты, на которые ссылаются поля.

  • Сделать все поля окончательными и частными.

  • Не позволяйте подклассам переопределять методы.

  • Если поля экземпляра включают ссылки на изменяемые объекты, не позволяйте этим объектам изменять:

    4,1. Не предоставляйте методы, изменяющие изменяемые объекты.

    4,2. Не используйте ссылки на изменяемые объекты. Никогда не храните ссылки на внешние, изменяемые объекты, переданные конструктору; при необходимости, создавать копии и хранить ссылки на копии. Аналогичным образом создайте копии своих внутренних изменяемых объектов, когда это необходимо, чтобы избежать возврата оригиналов в ваши методы.

Ответ 4

Вы можете сделать еще лучше с помощью

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

Ответ 5

В этом случае я бы предпочел не раскрывать список или карту вообще. Если вы выйдете из коллекции, вы нарушите инкапсуляцию, предположив, что она должна храниться в виде списка.

Если вы не раскрываете список, вы можете избежать необходимости его обернуть или предоставить защитную копию.

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

}

Предоставляя определенные методы, вы можете оптимизировать код для этого случая использования.

Ответ 6

Я думаю, что проблема с этим подходом заключается в том, что я все еще могу сделать getPersons(). add (x). Для этого не нужно создавать конструктор, который получает список лиц, а затем создает свой внутренний окончательный список, например, ImmutableList. Остальная часть кода мне подходит.

EDIT:

как указывает Давид Лоренцо МАРИНО, этого недостаточно. Вам также нужно будет вернуть глубокую копию списка в getter, чтобы пользователи не могли изменить ваш внутренний список по возвращаемой ссылке getter.

Посредством этого вы можете просто иметь обычный LinkedList как ваш текущий список (закрытый финал). На вашем конструкторе вы создаете глубокую копию списка и на своем получателе вы возвращаете глубокую копию своего внутреннего списка.