Comments 18
Первый вариант, по-моему, для того, чтобы вызывающий метод имел гарантии, что вызываемый его не поменяет. Не знаю, можно ли это реализовать конкретно в Java, но в некоторых случаях може быть полезным, например запустить три таких "чистых" метода параллельно или просто сначала выполнить самый важный для юзера, не боясь, что от порядка что-то изменится
Collections.unmodifiableList
. Просто он для такой ситуации ничем не лучше, чем по-настоящему immutable список (если последний достаточно быстрый). Зато с immutable взаимные ожидания методов более явно задекларированы. Хотя возможны и ситуации, когда immutable не подходит — как раз когда изменяемость нужна — вызываемый метод не может менять список, но знает, что его может менять вызывающий метод, и умеет это обрабатывать. Но это отдельные случаи, редкие и неприятные как раз необходимостью этой особой обработки.к сожалению это не совсем имутабельный объект получится.
Дело в том что объект имутабельный если он не меняет свое состояние, элементы списка это тоже часть состояния и они должны быть неизменны то есть тоже имутабельны, если на это забить то получится следующее
# в виде псевдо кода
1. создадим текущую дату в переменную current
2. поместим current в ImmutableList
3. достаним первый элемент списка и выведем дату
4. обратимся к переменной current и изменим дату
5. повторим пункт 3 и получим другой результат
И мы получили что наш "имутабельный" объект вызывая один и тот же метод с одинаковыми аргументами возвращает разное, чего быть не должно.
Это еще не считая шаманств с рефлекшен.
Получить «иммутабельный список иммутабельных объектов» — задача интересная, но вряд ли на Java решаемая. Хотя если в списке лежат только голые данные в виде POJO, то можно было бы сделать для них иммутабельные копии через рефлекшены. Но это очень долго и уже поэтому не нужно.
ну я не совсем согласен в изначальной статье на тему имутабельный список может содержать мутабельные объекты, тогда зачем нужен такой "имутабельный" список? вопрос конечно же философский.
Мне привычней классическая терминалогия, мутабельных или имутабельных объектов.
Есть вполне практические применения у иммутабельной коллекции мутабельных объектов — не нужно отслеживать добавление-удаление элементов (как правило путём клонирования оригинальной коллекции и хранения её где-то под капотом) — эти операции выносятся наружу и инвалидируют исходную коллекцию, а иммутабельность гарнтирует, что обойти их нельзя.
2) Ваша реализация похожа на грабительство — Вы отбираете массив у вызывающего объекта. Это антипаттерн.
3) На самом деле, ImmutableList-ы в большинстве библиотек построены на основе сбалансированных деревьев, поэтому, изменение листа проходит за O(log(N)).
4) «Можно считать, что посыл исходной статьи «неизменяемых коллекций в Java не будет» ошибочен.»
На основе Вашей статьи такого вывода сделать нельзя. Вы придумали кривой велосипед, который никто в здравом уме не примет за стандарт. Поэтому, изначальный посыл автора статьи остаётся актуальным.
2) Ваша реализация похожа на грабительство — Вы отбираете массив у вызывающего объекта. Это антипаттерн.Обоснуете, почему «анти»? Например, из ORM вы получили
ArrayList<MyEntity>
, понимаете, что меняться он не будет, и хотите дальше в своём коде использовать его как ImmutableList<MyEntity>
. Преобразовали, старый список ушёл, новый появился. Чем плохо?3) На самом деле, ImmutableList-ы в большинстве библиотек построены на основе сбалансированных деревьев, поэтому, изменение листа проходит за O(log(N)).Вот это вообще не понял. Какие изменения ImmutableList-а?
4) Вы придумали кривой велосипед, который никто в здравом уме не примет за стандарт.Кривой он только тем, что использует рефлекшены (если вдруг поменяют название поля
ArrayList#elementData
— поломается). Естественно, если бы это делали авторы Java, можно было бы без рефлекшенов обойтись.Вот это вообще не понял. Какие изменения ImmutableList-а?
Ну хотя бы вот такие:
interface ImmutableList<E> extends ReadOnlyList<E> {
ImmutableList<E> add(E item);
ImmutableList<E> add(int index, E item);
ImmutableList<E> remove(E item);
ImmutableList<E> remove(int index);
ImmutableList<E> set(int index, E item);
}
А в чём проблема вызвать в цикле метод, работающий за O(log N)?
Например, из ORM вы получилиArrayList<MyEntity>
, понимаете, что меняться он не будет, и хотите дальше в своём коде использовать его какImmutableList<MyEntity>
. Преобразовали, старый список ушёл, новый появился. Чем плохо?
Основное что плохо — неочевидность того, что простой вызов new ImmutableList<MyEntity>(x)
может очистить x
.
А еще можно просто взять Vavr...
public interface ReadOnlyCollection extends Iterable
Как я уже писал, Iterable не дает гарантии ReadOnly, т.к. в Iterator-е есть метод remove(). Впринципе на этом можно ставить точку. Нужен ReadOnlyIterable и ReadOnlyIterator.
метод получает UnmodifiableList, сам его менять не может, но знает, что содержимое может быть изменено другой нитью (и умеет это корректно обрабатывать)
Нити здесь ни при чем. UnmodifiableList — это только гарантии того, что консьюмер не может изменить список, то же самое, что и Collections.unmodifiableList(), только на уровне деклараций. Для полностью неизменяемого списка служит именно ImmutableList.
ImmutableList и List не должны быть связаны отношениями наследования. Почему так – разбирается в исходной статье.
Изначально вопрос стоял именно в этом: что будет, если наследовать и что при этом отвалится. Всегда можно нагородить своих библиотек, не связанных напрямую со стандартными коллекциями (это как один из вариантов решения — сделать новый java.collections и добавить конвертеров), но изначальная идея — это исправить уже существующие. И основной кейс использования — это при возврате кастить именно в UnmodifiableCollection:
public class Department {
private List<Person> persons;
/** мы не хотим, чтобы консьюмер изменял список, и въявную это декларируем */
public UnmodifiableList<Person> getPersons() {
// кастится, поскольку List должен наследоваться от UnmodifiableList
// в противном случае пришлось бы врапить new UnmodifiableList(persons)
return persons;
}
/** потому что добавление в список должно быть контролируемым, и только через этот метод. типичный кейс для JPA */
public void addPerson(Person person) {
person.setDepartment(this);
persons.add(person);
}
public void setPersons(UnmodifiableList<Persons> persons) {
// Так вообще нельзя делать, т.к. клиент ничего не должен знать о внутренней реализации списка
// this.persons = persons;
// Так тоже нельзя, потому как может быть persons == this.persons
// this.persons.clear();
this.persons = new ArrayList<>();
persons.stream().forEach(this::addPerson);
}
Остальное — детали реализации, которые впринципе не важны.
Iterable не дает гарантии ReadOnly, т.к. в Iterator-е есть метод remove()Ну так Iterator-то мы сами пишем… Метод remove() кидает UnsupportedOperationException. Безусловно, с ReadOnlyIterator было бы красивее, но приходится подстраиваться под существующую Java — Iterable нужен, чтобы можно было написать
for (Item item : ImmutableList<Item>)
сделать новый java.collections и добавить конвертеровЭто непрактично, т.к. все библиотеки используют «старые» коллекции, а конвертация — тяжёлая операция. Основная идея того, что я описал в статье — как раз «дешёвая» конвертация туда-обратно.
Рукоблудие вокруг ImmutableList в Java