Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Reason not to use a guarded/constrained collection

Is there any reasons/arguments not to implement a Java collection that restricts its members based on a predicate/constraint?

Given that such functionality should be necessary often, I was expecting it to be implemented already on collections frameworks like apache-commons or Guava. But while apache indeed had it, Guava deprecated its version of it and recommend not using similar approaches.

The Collection interface contract states that a collection may place any restrictions on its elements as long as it is properly documented, so I'm unable to see why a guarded collection would be discouraged. What other option is there to, say, ensure a Integer collection never contains negative values without hiding the whole collection?

like image 703
gcscaglia Avatar asked Jul 31 '15 14:07

gcscaglia


2 Answers

It is just a matter of preference -look at thread about checking before vs checking after - I think that is what it boils down to. Also checking only on add() i good enough only for immutable objects.

like image 123
user158037 Avatar answered Nov 06 '22 02:11

user158037


There can hardly be one ("acceptable") answer, so I'll just add some thoughts:

As mentioned in the comments, the Collection#add(E) already allows for throwing an IllegalArgumentException, with the reason

if some property of the element prevents it from being added to this collection

So one could say that this case was explicitly considered in the design of the collection interface, and there is no obvious, profound, purely technical (interface-contract related) reason to not allow creating such a collection.


However, when thinking about possible application patterns, one quickly finds cases where the observed behavior of such a collection could be ... counterintuitive, to say the least.

One was already mentioned by dcsohl in the comments, and referred to cases where such a collection would only be a view on another collection:

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));

However, one could argue that

  • Such a collection would not necessarily have to be only a view. Instead, one could enforce that only new collections with such constraints may be created
  • The behavior is similar to that of Collections.unmodifiableCollection(Collection), which is widely anticipated as it is. (Although it serves a far broader and omnipresent use-case, namely avoiding the internal state of a class to be exposed by returning a modifiable version of a collection via an accessor method)

But in this case, the potential for "inconsistencies" is much higher.

For example, consider a call to Collection#addAll(Collection). It also allows throwing an IllegalArgumentException "if some property of an element of the specified collection prevents it from being added to this collection". But there are no guarantees about things like atomicity. To phrase it that way: It is not specified what the state of the collection will be when such an exception was thrown. Imagine a case like this:

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?

(It may be subtle, but it may be an issue).

All this might become even trickier when the condition changes after the collection has been created (regardless of whether it is only a view or not). For example, one could imagine a sequence of calls like this:

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

Where in someMethod(), there is an innocent line like

predicate.setForbiddingNegatives(true);

One of the comments already mentioned possible performance issues. This is certainly true, but I think that this is not really a strong technical argument: There are no formal complexity guarantees for the runtime of any method of the Collection interface, anyhow. You don't know how long a collection.add(e) call takes. For a LinkedList it is O(1), but for a TreeSet it may be O(n log n) (and who knows what n is at this point in time).

Maybe the performance issue and the possible inconsistencies can be considered as special cases of a more general statement:

Such a collection would allow to basically execute arbitrary code during many operations - depending on the implementation of the predicate.

This may literally have arbitrary implications, and makes reasoning about algorithms, performance and the exact behavior (in terms of consistency) impossible.


The bottom line is: There are many possible reasons to not use such a collection. But I can't think of a strong and general technical reason. So there may be application cases for such a collection, but the caveats should be kept in mind, considering how exactly such a collection is intended to be used.

like image 29
Marco13 Avatar answered Nov 06 '22 02:11

Marco13