4

I'm trying to filter a sorted stream using java9´s takeWhile to get objects from the beginning of the stream that share the same value for a field. I am unable to write the right predicate to do so. This can be done using two steps breaking the pipeline of the stream.

Stream.of(objA, objB, objC, objD, objE, objF, objG)
    .takeWhile(" get the value of objA´s field and take 
   as long as as other objects value for that field is the same as objA´s");

In two steps I could do something like

int x = Stream.of(objA, objB, objC, objD, objE, objF, objG).findFirst().get().getSomeValue();

Stream.of(objA, objB, objC, objD, objE, objF, objG).takeWhile(e -> e.getSomeValue() == x);

A simplified example could be

Stream.of(5,5,5,5,13,14,5,5,2,5,5,6)
.takeWhile(get the first four '5'´s)

Can this be done without the intermediate step using optional.get?

Stefan Zobel
  • 3,182
  • 7
  • 28
  • 38
nopens
  • 721
  • 1
  • 4
  • 20
  • @ernest_k I get it passed as a parameter in a method. The code where the stream is built uses a library called streamex, which i have no experience with. the stream is not infinite even though the length of the stream is not known beforehand. – nopens Nov 01 '19 at 20:38
  • Is this the final step or do you need additional stream operations after `takeWhile`? – Glains Nov 01 '19 at 20:43
  • sorry still i don't get your question @nopens – Ryuzaki L Nov 01 '19 at 20:45
  • @Glains No more complicated stuff after that. I need to collect those objects to a list, that would be the final step. – nopens Nov 01 '19 at 20:48

1 Answers1

3

A (bit clumsy) workaround is to use a mutable holder object (because the reference must be effectively final) that contains either null or the first value. We'll use AtomicReference in this example.

AtomicReference<MyClass> holder = new AtomicReference<>();

sortedStream.takeWhile(e -> holder.get() == null || holder.get().equals(e))
.map(e -> { holder.set(e); return e; })
.collect(Collectors.toList());

Now this still contains an extra step, but as a Stream can only be processed once, the findFirst() approach won't work anyway. This also keeps setting the holder object even if it has been set, but that's just a minor annoyance rather than a problem.

As Holger pointed out, a better and more streamlined version would be

sortedStream.takeWhile(e -> holder.compareAndSet(null, e.getSomeValue()) || e.getSomeValue() == holder.get())
.collect(Collectors.toList());

where with the first element compareAndSet assigns the value, returning true, and the subsequent elements are compared against the holder's value.

If you do use this approach, I recommend adding a comment pointing to this question/answer, or at least somehow try to explain what this code achieves, as it may not be immediately obvious to people reading the code.

Kayaman
  • 72,141
  • 5
  • 83
  • 121
  • 3
    Been trying to make a similar solution work with little 'clumsiness'... I think this will make it better: `.takeWhile(e -> null == holder.getAndUpdate(i -> i != null ? i : e.getSomeValue()) || e.getSomeValue() == holder.get())` - no need to repurpose `map` – ernest_k Nov 01 '19 at 21:00
  • I just tried it and it works fine. To be honest, I'm still trying to figure out what's going on. I'm definitely going to write notes and add the link. Thank you very much. – nopens Nov 01 '19 at 21:03
  • 1
    @ernest_k well, "better" is quite subjective. It certainly doesn't make it more readable, but this is the ugly part regardless of how it's done. I would probably go with `map()` just to try to keep it a *bit* more readable, but tomato / tomato, it's not going to be pretty in any case. – Kayaman Nov 01 '19 at 21:06
  • @ernest_k Thanks for your comment. I think with your addition I understand even better how you two solved this. – nopens Nov 01 '19 at 21:09
  • 2
    @ernest_k or just `.takeWhile(e -> holder.compareAndSet(null, e.getSomeValue()) || e.getSomeValue() == holder.get())` – Holger Nov 02 '19 at 12:10