I need to expose an API - consume(sequence)
below - which mandates that its argument sequence
collection be ordered as in the below excerpt:
interface Consumer<T> {
/**
* @param sequence: an *ordered* collection of Ts to be processed in order
*/
public void consume(Collection<T> sequence);
}
interface Producer<T> {
Collection<T> getSequence();
}
class Producer1<T> implements Producer<T> {
public List<T> getSequence() {
return new ArrayList<>();
}
}
class Producer2<T> implements Producer<T> {
public Deque<T> getSequence() {
return new LinkedList<>();
}
}
class Test {
void testMethod(Consumer<Long> consumer) {
consumer.consume(new Producer1<Long>().getSequence());
consumer.consume(new Producer2<Long>().getSequence());
}
}
Typically one would specify consume()
as accepting a List
; however, some producers also expose a Deque
, so as to facilitate things like efficient reverse-iteration using descendingIterator()
. However Deque
doesn't extend List
and there are likely good reasons for this (O(n) cost of accessing an indexed element in a LinkedList
).
So it seems the only way to "keep the compiler happy" is to specify sequence
as a Collection
; however, according to the Javadoc (and as we all know), a "some are ordered and others unordered", so the consume()
API looses in semantics.
Another workaround would be to have Producer2
expose a LinkedList
rather than a Deque
(and revert consume()
to accept a List
) but we know it's not ideal to expose implementations rather than interfaces.
It seems the ideal solution would be for Java to provide a Sequence
super-interface to List
and Deque
(extending Iterable
). I can imagine one reason this wasn't done is complexity but I think this example demonstrates the need.
Am I missing a better strategy here or do I just need to wait for a revision of the API? For the record, this is Java 17.
As predicted in the Answer by Rob Spoor, Sequenced Collections were delivered in Java 21. See JEP 431 for details.
You wanted a standard interface that would cover both List
& Deque
as representing a sequence.
Now you have it. In Java 21 and later, SequencedCollection
is a super-interface with both List
& Deque
as sub-interfaces. So concrete classes such as ArrayList
, LinkedList
, and ArrayDeque
can all be referred to as a SequencedCollection
.
SequencedCollection<String> x = new ArrayList<>() ;
SequencedCollection<String> y = new LinkedList<>() ;
SequencedCollection<String> z = new ArrayDeque<>() ;
Specific to your Question, you can now declare your consume
method as taking a SequencedCollection
rather than the more general Collection
.
public void consume( SequencedCollection<T> sequence );
Those classes gained new features. All implementations of SequencedCollection
offer methods to add, get, and remove elements from the first and last positions. They also offer a reversed
method to iterate in opposite order.
For more info, see excellent presentation by Stuart Marks, the lead on JEP 431. Here is his class diagram: