Защитить ArrayList от доступа для записи
Рассмотрим следующий класс:
public class Cars extends Observable{
private ArrayList<String> carList = new ArrayList<String>();
public void addToCarList(String car){
// ...
hasChanged();
notifyObservers();
}
public void removeFromCarList(String car){
// ...
hasChanged();
notifyObservers();
}
public ArrayList<String> getCarList() {
return carList;
}
}
Как вы можете видеть, каждый раз, когда изменен carList, я хочу уведомить Observers
.
Если кто-то делает getCarList().add(...);
, это обойдется.
Как я могу предоставить доступ для чтения к carList
(для итерации по нему и т.д.), но запретить доступ к записи, кроме специальных методов addToCarList
и removeFromCarList
?
Я подумал об этом:
public ArrayList<String> getCarList() {
return (ArrayList<String>)carList.clone();
}
но кто-то, использующий мой класс, при добавлении чего-то к клону carList
не должен быть проинформирован о том, что это не так, как это должно было быть сделано.
Ответы
Ответ 1
Вы можете вернуть немодифицируемое представление об этом, изменив тип возврата на List<String>
вместо ArrayList<String>
:
public List<String> getCars() {
return Collections.unmodifiableList(carList);
}
Обратите внимание, что поскольку Collections.unmodifiableList
предоставляет только представление, вызывающий абонент все равно увидит любые другие изменения, сделанные с помощью addToCarList
и removeFromCarList
(который я бы переименовал в addCar
и removeCar
, возможно). Это то, что вы хотите?
Любые мутирующие операции на возвращенном представлении приведут к UnsupportedOperationException
.
Ответ 2
Во-первых, всегда избегайте использования конкретного класса в левой части назначения и в качестве возвращаемого значения метода. Итак, исправьте свой класс как
public class Cars extends Observable{
private List<String> carList = new ArrayList<String>();
........................
public List<String> getCarList() {
return carList;
}
}
Теперь вы можете использовать Collections.unmodifiableList()
, чтобы сделать список доступным только для чтения:
public List<String> getCarList() {
return Collections.unmodifiableList(carList);
}
Кстати, если вам действительно не нужно возвращать List
, вы можете вернуться Collection
или даже Iterable
. Это увеличит уровень инкапсуляции вашего кода и облегчит будущие изменения.
Ответ 3
Ответ на Jon Skeet превосходный (как всегда), но единственное, на что он не касается, - это проблемы concurrency.
Возвращение немодифицируемой коллекции все равно оставит вас с проблемами, если несколько потоков обращаются к этому объекту в одно и то же время. Например, если один поток выполняет итерацию по списку автомобилей, и в то же время другой поток добавляет новую карту.
Вам все равно нужно синхронизировать доступ к этому списку, и это одна из причин, по которой вы можете рассмотреть возможность возврата clone()
списка, а также вместо того, чтобы просто обернуть его в обертку unmodifiableList
. Вам все равно нужно будет синхронизировать вокруг clone()
, но как только клон будет завершен, и список вернется в код запроса, его больше не нужно синхронизировать.
Ответ 4
Я думаю, вы, вероятно, могли бы сделать свой Object реализацией Collection-Interface, если это фактически ObservableList. Это список, и он должен быть Observable - поэтому он должен реализовывать оба интерфейса.
Вы можете даже расширить список <.. > , потому что просто хотите добавить дополнительную функциональность (наблюдатели) к текущим функциям, и ваш список можно использовать везде, где может использоваться обычный список...
Ответ 5
используйте Collections.unmodifiableList(list), поскольку он предоставляет новый объект List, который не может быть изменен, он будет генерировать исключение UnsupportedOperationException при попытке обновить/добавить/удалить список объектов.