Как 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;
}