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

Причина не использовать охраняемую/ограниченную коллекцию

Есть ли какие-либо причины/аргументы для реализации коллекции Java, которая ограничивает ее члены на основе предиката/ограничения?

Учитывая, что такая функциональность должна быть необходима часто, я ожидал, что она будет реализована уже в рамках коллекций, таких как apache-commons или Guava. Но в то время как apache действительно имел это, Guava отказался от своей версии и не рекомендует использовать аналогичные подходы.

В контракте Collection interface указано, что коллекция может накладывать какие-либо ограничения на ее элементы, если она должным образом задокументирована, поэтому я не могу понять, почему охраняемая сбор будет обескуражен. Какой еще вариант, например, обеспечить, чтобы коллекция Integer никогда не содержала отрицательные значения, не скрывая всю коллекцию?

4b9b3361

Ответ 1

Это просто вопрос предпочтения в вопросе проверки перед проверкой после проверки - я думаю, что это то, к чему это сводится. Также проверяя только на add() я достаточно хорошо только для неизменяемых объектов.

Ответ 2

Вряд ли может быть один ( "приемлемый" ) ответ, поэтому я просто добавлю некоторые мысли:

Как уже упоминалось в комментариях, Collection#add(E) уже позволяет бросать IllegalArgumentException, по причине

если какое-либо свойство элемента предотвращает его добавление в эту коллекцию

Итак, можно сказать, что этот случай был явно рассмотрен в дизайне интерфейса коллекции, и нет очевидной, глубокой, чисто технической (связанной с интерфейсом) причине, чтобы не создавать такую ​​коллекцию.


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

Один из них уже упоминался в комментариях dcsohl и ссылался на случаи, когда такая коллекция была бы только представлением для другой коллекции:

List<Integer> listWithIntegers = new ArrayList<Integer>();

List<Integer> listWithPositiveIntegers = 
    createView(listWithIntegers, e -> e > 0);

//listWithPositiveIntegers.add(-1); // Would throw IllegalArgumentException
listWithIntegers.add(-1); // Fine

// This would be true:
assert(listWithPositiveIntegers.contains(-1));

Однако можно утверждать, что

  • Такая коллекция не обязательно должна быть только точкой зрения. Вместо этого можно было бы установить, что могут быть созданы только новые коллекции с такими ограничениями.
  • Поведение аналогично поведению Collections.unmodifiableCollection(Collection), которое широко известно как оно есть. (Хотя он служит гораздо более широкому и вездесущему варианту использования, а именно, избегая внутреннего состояния класса, которое должно быть показано, возвращая модифицируемую версию коллекции с помощью метода доступа).

Но в этом случае потенциал "несоответствий" намного выше.

Например, рассмотрите вызов Collection#addAll(Collection). Он также позволяет бросать IllegalArgumentException ", если какое-либо свойство элемента указанной коллекции не позволяет добавить его в эту коллекцию". Но нет никаких гарантий относительно таких вещей, как атомарность. Чтобы сформулировать это так: не указано, каким будет состояние коллекции, когда было отправлено такое исключение. Представьте себе такой случай:

List<Integer> listWithPositiveIntegers = createList(e -> e > 0);

listWithPositiveIntegers.add(1); // Fine
listWithPositiveIntegers.add(2); // Fine
listWithPositiveIntegers.add(Arrays.asList(3,-4,5)); // Throws

assert(listWithPositiveIntegers.contains(3)); // True or false?
assert(listWithPositiveIntegers.contains(5)); // True or false?

(Это может быть тонким, но это может быть проблемой).

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

List<Integer> listWithPredicate = create(predicate);
listWithPredicate.add(-1); // Fine 
someMethod();
listWithPredicate.add(-1); // Throws

Где в someMethod() есть невинная строка, например

predicate.setForbiddingNegatives(true);

В одном из комментариев уже упоминались возможные проблемы с производительностью. Это, конечно, правда, но я думаю, что это не очень сильный технический аргумент: во всяком случае, никаких формальных ограничений сложности для среды выполнения любого метода интерфейса Collection нет. Вы не знаете, сколько времени занимает вызов collection.add(e). Для a LinkedList это O (1), но при a TreeSet это может быть O (n log n) (и кто знает, что n находится в этот момент времени).

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

Такая коллекция позволит в основном выполнять произвольный код во время многих операций - в зависимости от реализации предиката.

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


Суть в том, что существует множество возможных причин не использовать такую ​​коллекцию. Но я не могу придумать сильную и общую техническую причину. Таким образом, для такой коллекции могут быть случаи применения, но следует помнить о предостережениях, учитывая, как именно такая коллекция предназначена для использования.

Ответ 3

Я бы сказал, что такая коллекция будет иметь слишком много обязанностей и будет нарушать SRP.

Основная проблема, которую я вижу здесь, - это читаемость и ремонтопригодность кода, который использует коллекцию. Предположим, у вас есть коллекция, в которую вы можете добавлять только положительные целые числа (Collection<Integer>), и вы используете ее во всем коде. Затем изменяются требования, и вам разрешено добавлять к нему нечетные положительные целые числа. Поскольку нет проверок времени компиляции, вам будет намного сложнее найти все вхождения кода, в который вы добавляете элементы в эту коллекцию, чем это было бы, если бы у вас был отдельный класс-оболочка, который инкапсулирует коллекцию.

Хотя, конечно, он даже не близок к такому пределу, он имеет некоторое сходство с использованием ссылки Object для всех объектов в приложении.

Лучший подход - использовать проверки времени компиляции и следовать хорошо зарекомендовавшим себя принципам ООП, таким как безопасность и инкапсуляция типов. Это означает создание отдельного класса-оболочки или создание отдельного типа для элементов коллекции.

Например, если вы действительно хотите убедиться, что работаете только с положительными целыми числами в контексте, вы можете создать отдельный тип PositiveInteger extends Number, а затем добавить их в Collection<PositiveInteger>. Таким образом, вы получаете безопасность во время компиляции, а преобразование PositiveInteger в OddPositiveInteger требует гораздо меньших усилий.

Enums - отличный пример предпочтительного использования выделенных типов и значений времени исполнения (постоянные строки или целые числа).