2

I want to convert this while loop to equivalent code using a Java 8 Streams, but I don't know how to both stream the List and remove elements from it.

private List<String> nameList = new ArrayList<>();

while (nameList.size() > 0) {
    String nameListFirstEntry = nameList.get(0);
    nameList.remove(0);
    setNameCombinations(nameListFirstEntry);
}
Eran
  • 387,369
  • 54
  • 702
  • 768

2 Answers2

6

I guess this will do

nameList.forEach(this::setNameCombinations);
nameList.clear();

In case you don't need the original list anymore, you might as well create a new empty list instead.

Gerald Mücke
  • 10,724
  • 2
  • 50
  • 67
  • 2
    Doesn't fullfill the requirement _stream the list and remove elements from it_. – Flown Apr 11 '17 at 13:33
  • ok, iterating AND deletion are two seperate concerns, I wonder why both have to be satisfied in the same stream? the actual question was "convert while loop to equivalent code using stream api", and there you go. You never asked for doing it in the same stream ... which I doubt being a sensible requirement anyway (though I have to admit Bohemian's approach is quite interessting) – Gerald Mücke Apr 11 '17 at 13:36
  • You're right, but the question also says _but I don't know how to both stream the list and remove elements from it_. – Flown Apr 11 '17 at 13:47
  • Stream API is a reading API, it's not intended for modifying the source. – Gerald Mücke Apr 11 '17 at 13:53
  • Do not get me wrong, I really like your clean and concise approach, but I think [Bohemian's answer](http://stackoverflow.com/a/43345980/4105457) fulfills the requirements. – Flown Apr 11 '17 at 13:57
  • 3
    Yes, agreed. His solution is clever and perfectly fulfills the requirement. The requirements (contraints) are nonsense nonetheless. It's one of those theoretical examples where you think "nice to know, but never do this in practice" ;) – Gerald Mücke Apr 11 '17 at 14:01
  • 1
    @Gerald You're correct that a stream shouldn't modify the source, but that's not what's going on in my code. The stream in my answer does not modify the source. The supplier does, which is completely different. The stream code is deterministic and obeys guidelines. The supplier code is simple and could even have used a method reference, but the code is briefer as is. – Bohemian Apr 11 '17 at 14:13
4

Because List#remove(int) also returns the element, you can both stream the list's elements and remove them via a stream:

Stream.generate(() -> nameList.remove(0))
    .limit(nameList.size())
    .forEach(this::setNameCombinations);

This code doesn't break any "rules". From the javadoc of Stream#generate():

Returns an infinite sequential unordered stream where each element is generated by the provided Supplier. This is suitable for generating constant streams, streams of random elements, etc.

There is no mention of any restrictions on how the supplier is implemented or that is must have no side effects etc. The Supplier's only contract is to supply.


For those who doubt this is "works", here's some test code using 100K elements showing that indeed order is preserved:

int size = 100000;
List<Integer> list0 = new ArrayList<>(size); // the reference list
IntStream.range(0, size).boxed().forEach(list0::add);
List<Integer> list1 = new ArrayList<>(list0); // will feed stream
List<Integer> list2 = new ArrayList<>(size);  // will consume stream

Stream.generate(() -> list1.remove(0))
        .limit(list1.size())
        .forEach(list2::add);

System.out.println(list0.equals(list2)); // always true
Bohemian
  • 412,405
  • 93
  • 575
  • 722
  • @Eugene So don't add `parallel()`. It is not "illegal", or even mysterious. It is using `List#remove()` as the stream's Supplier... big deal. Try it yourself if you have doubts. – Bohemian Apr 11 '17 at 12:51
  • @eugene Let's get something straight here: This code does not stream the list, and the stream is not modifying anything. The Supplier is mutating something as it supplies, but the stream doesn't know or care how the Supplier supplies. There is nothing here that breaks any guidelines or contracts. – Bohemian Apr 11 '17 at 13:00
  • @Eugene why do you believe that adding parallel should get the same result? Where does it say that? I can tell you that adding parallel usually does change the result - that is the encounter order of the elements of the stream becomes non-deterministic, if the particular stream supports parallelism (the stream if a List for example does not). – Bohemian Apr 11 '17 at 13:09
  • assuming the `nameList` is made out of 3 elements `[a,b,c]`; by the same result Im expecting to see these 3 elements in any order (since it's parallel) – Eugene Apr 11 '17 at 13:13
  • @OleV.V. How is it possible to exhaust this `List` except another thread? This has to be prohibited by a proper synchronization. – Flown Apr 11 '17 at 13:35
  • @Bohemian To ensure encounting order of elements you should use `Stream::forEachOrdered`. – Flown Apr 11 '17 at 13:35
  • @Flown adding `forEachOrdered` can still fail in a parallel pipeline with `IndexOutOfBoundsException`. – Eugene Apr 11 '17 at 13:39
  • @Eugene If you're removing the exact amount of elements of the `List` how is this possible? – Flown Apr 11 '17 at 13:40
  • @Flown (even if I can re-produce that quite easily right now - to throw an IndexOutOfBoundsException), the point is that `nameList` is a non-thread safe collection, `remove` is not `atomic` - the result later is completely un-defined. – Eugene Apr 11 '17 at 13:43
  • @Eugene As I said you have to do proper synchronization during `Stream` processing, but this is a general problem not specific to this question. – Flown Apr 11 '17 at 13:46
  • @Eugene see update to question with test code that shows this with works, preserving order, with 100K elements. See if you can write a test case that doesn't work as expected – Bohemian Apr 11 '17 at 14:03
  • @Flown see update to question with test code that shows this with works, preserving order, with 100K elements. See if you can write a test case that doesn't work as expected – Bohemian Apr 11 '17 at 14:06
  • @Bohemian `List nameList = new ArrayList<>(); nameList.add("a"); nameList.add("b"); nameList.add("c"); Stream.generate(() -> nameList.remove(0)) .parallel() .limit(nameList.size()) .forEachOrdered(System.out::println);` let's make that much simpler. – Eugene Apr 11 '17 at 14:06
  • @Eugene huh? I deliberately didn't use `parallel()` because OP's code is order-sensitive. Why do you keep introducing it when it's not part of my answer? Also, this is not about creating a stream that may be used elsewhere. This is about replacing a specific isolated block of code, with the Stream used in-place then discarded. – Bohemian Apr 11 '17 at 14:09
  • @Eugener, shouldn't `parallel()` stage come after `limit()`, not before? – M. Prokhorov Apr 11 '17 at 14:11
  • 1
    @M.Prokhorov it does not matter. – Eugene Apr 11 '17 at 14:12
  • @Bohemian `Stream::forEachOrdered` should be used, if you want to switch to `Stream::parallel`. – Flown Apr 11 '17 at 14:15
  • @Bohemian parallel was added to prove that this violates some spec. Also see this: http://stackoverflow.com/questions/29216588/how-to-ensure-order-of-processing-in-java8-streams/29218074#29218074. I will make this my last comment also – Eugene Apr 11 '17 at 14:16