Как Collections.unmodifiableList (Java 8 SE) изменяется при создании нового ArrayList из него?

У меня есть получатель, который возвращает неизменяемый список, как таковой:

public List<Product> getProductList() {
    if (productList == null)
        return new ArrayList<>();
    return Collections.unmodifiableList(productList);
}

Я называю этот геттер как таковой:

List<Product> productList = new ArrayList<>(report.getProductList());

Затем я передаю этот список другому методу, который изменяет список следующим образом:

for (Product product : productList) {
    product.addToAdvisoryList(advisory);
}

где addToAdvisoryList (Консультативный совет) - это:

public void addToAdvisoryList(Advisory advisory) {
    if (advisoryList == null) {
        setAdvisoryList(Collections.singletonList(advisory));
    } else if (!isContainedAdvisory(advisoryList, advisory)) {
        List<Advisory> newAdvisoryList = new ArrayList<>(advisoryList);
        newAdvisoryList.add(advisory);
        setAdvisoryList(newAdvisoryList);
    }
}

После запуска этого кода оригинальный список продуктов изменяется. Может кто-нибудь объяснить, что именно произошло? и что можно сделать, чтобы избежать изменения неизменяемого списка?

2 ответа

Решение

В предоставленном вами коде исходный (неизменяемый) список передается в качестве аргумента в конструктор java.util.ArrayListМодифицируемый список.

List<Advisory> newAdvisoryList = new ArrayList<>(advisoryList);

Этот конструктор создает новый список со всеми элементами предоставленного Collection в порядке, возвращаемом его итератором (см. документацию).

То же самое происходит со списком продуктов:

List<Product> productList = new ArrayList<>(report.getProductList());

Это делает излишним возвращать неизменяемые списки в первую очередь.

Другими словами, вы не изменяете неизменяемые списки. Вы создаете новые списки, которые можно изменить, используя элементы из неизменяемых (неизменяемых) списков, а затем изменяете (изменяемые) списки.

Также смотрите здесь для неизменяемых списков, из документации Java. Вы получите UnsupportedOperationException при попытке изменить неизменяемый список.

РЕДАКТИРОВАТЬ

Чтобы ответить на ваш вопрос о добытчиках:

"Разве целью геттера не является получение доступной только для чтения копии объекта?"

Получатель используется для обеспечения доступа к частному или защищенному члену класса извне.

Если у вас есть класс, как (в вашем примере) Report

public class Report {

    private List<Product> products;

    public void setProducts(List<Product> products) {
        this.products = products;
    }

    public List<Product> getProducts() {
        return products;
    }

}

getProducts делает список доступным извне класса (так что вы можете добавлять или удалять элементы, если он изменчив). Подобные вещи обычно выполняются с Java bean-компонентами, хотя их использование обсуждается (рассмотрите возможность замены методов получения и установки методами, которые не показывают информацию о состоянии без необходимости). В любом случае, смотрите здесь для получения дополнительной информации о методах получения и установки согласно Oracle.

Ваши списки содержат изменяемые объекты Product.

Вы не изменяете ни один из списков. Списки содержат ссылки на одни и те же изменяемые объекты, и вы вносите изменения в эти объекты. Это в основном та же старая проблема, что и

Чтобы избежать этого, вы можете сделать getProductList вернуть список копий. В качестве альтернативы вы можете сделать класс Product неизменным, что заставит вас пересмотреть свой подход.

public List<Product> getProductList() {
    List<Product> copies = new ArrayList<>();
    for (Product product: productList)
        copies.add(new Product(product)); // add this constructor

    return copies;
}
Другие вопросы по тегам