How to increment a value in Java Stream?

11,075

Solution 1

You shouldn't. These two look similar, but they are conceptually different. The loop is just a loop, but a forEach instructs the library to perform the action on each element, without specifying neither the order of actions (for parallel streams) nor threads which will execute them. If you use forEachOrdered, then there are still no guarantees about threads, but at least you have the guarantee of happens-before relationship between actions on subsequent elements.

Note especially that the docs say:

For any given element, the action may be performed at whatever time and in whatever thread the library chooses. If the action accesses shared state, it is responsible for providing the required synchronization.

As @Marko noted in the comments below, though, it only applies to parallel streams, even if the wording is a bit confusing. Nevertheless, using a loop means that you don't even have to worry about all this complicated stuff!

So the bottom line is: use loops if that logic is a part of the function it's in, and use forEach if you just want to tell Java to “do this and that” to elements of the stream.

That was about forEach vs loops. Now on the topic of why the variable needs to be final in the first place, and why you can do that to class fields and array elements. It's because, like it says, Java has the limitation that anonymous classes and lambdas can't access a local variable unless it never changes. Meaning not only they can't change it themselves, but you can't change it outside them as well. But that only applies to local variables, which is why it works for everything else like class fields or array elements.

The reason for this limitation, I think, is lifetime issues. A local variable exists only while the block containing it is executing. Everything else exists while there are references to it, thanks to garbage collection. And that everything else includes lambdas and anonymous classes too, so if they could modify local variables which have different lifetime, that could lead to problems similar to dangling references in C++. So Java took the easy way out: it simply copies the local variable at the time the lambda / anonymous class is created. But that would lead to confusion if you could change that variable (because the copy wouldn't change, and since the copy is invisible it would be very confusing). So Java just forbids any changes to such variables, and that's that.

There are many questions on the final variables and anonymous classes discussed already, like this one.

Solution 2

Some kind of "zip" operation would be helpful here, though standard Stream API lacks it. Some third-party libraries extending Stream API provide it, including my free StreamEx library:

IntStreamEx.ints() // get stream of numbers 0, 1, 2, ...
           .boxed() // box them
           .zipWith(StreamEx.ofValues(map)) // zip with map values
           .forKeyValue((index, item) -> image[index].setImage(item.getImage()));

See zipWith documentation for more details. Note that your map should have meaningful order (like LinkedHashMap), otherwise this would be pretty useless...

Share:
11,075
Nikolas Charalambidis
Author by

Nikolas Charalambidis

My personal rules of participation: I no longer downvote questions/answers and leave a comment instead For each question I ask, I help 10 other people through my answers.

Updated on July 24, 2022

Comments

  • Nikolas Charalambidis
    Nikolas Charalambidis almost 2 years

    I want to increment value of index with the each iteration by 1. Easily to be achieved in the for-loop. The variable image is an array of ImageView.

    Here is my for-loop.

    for (Map.Entry<String, Item> entry : map.entrySet()) {      
        image[index].setImage(entry.getValue().getImage());
        index++;
    }
    

    In order to practise Stream, I have tried to rewrite it to the Stream:

    map.entrySet().stream()
        .forEach(e -> item[index++].setImage(e.getValue().getImage()));
    

    Causing me the error:

    error: local variables referenced from a lambda expression must be final or effectively final

    How to rewrite the Stream incrementing the variable index to be used in?

  • Marko Topolnik
    Marko Topolnik over 7 years
    Stream pipelines may execute either sequentially or in parallel. This execution mode is a property of the stream. Streams are created with an initial choice of sequential or parallel execution. There is a guarantee that OP's stream will execute sequentially. Doesn't matter on which thread, and ordering doesn't matter either since the source is a hashtable.
  • Marko Topolnik
    Marko Topolnik over 7 years
    A captured local variable is transformed into a field on the lambda instance. The problem that would happen with non-final variables is that updating the lambda field wouldn't be reflected on the value of the local variable. So non-final would force you to somehow refer to the location on the stack, and then you'd get into trouble.
  • Holger
    Holger over 7 years
    @Marko Topolnik: it has never been said that the source is a hashtable. If the source Map has an unspecified ordering, we can consider the entire thing, the OP does, broken, as it is intended to update pre-existing objects in an array whose only identity is determined by the array’s ordering.
  • Marko Topolnik
    Marko Topolnik over 7 years
    @Holger You're probably right about the map being sorted, but assuming hashtable isn't necessarily broken. The objects in the array are just GUI components. You may not care about the order of images as long as all are there.
  • Holger
    Holger over 7 years
    @Marko Topolnik: there has to be a pre-existing relationship between the array elements and the map elements, otherwise, who’s to guaranty that the array has the right size? I guess, the whole thing is an xy problem, the array creation has been omitted in the question whereas it would be much better to let the stream create the array in the first place. Otherwise, there is no benefit in using a stream at all, especially created from the entry set. A map.values().forEach(…) would eliminate any sequential vs parallel questions (and the obsolete e.getValue() call)…
  • Marko Topolnik
    Marko Topolnik over 7 years
    @Holger There isn't necessarily a pre-existing relationship to the identity of each array member.
  • Sergei Tachenov
    Sergei Tachenov over 7 years
    @Marko, to your first comment: you're right, I've added a note that undefined order applies only to parallel streams. I don't quite understand what do you mean by “doesn't matter on which thread”. Certainly it does matter if you do all kinds of things inside the consumer. The docs explicitly say: “ If the action accesses shared state, it is responsible for providing the required synchronization.” I'll edit to add that quote too.
  • Marko Topolnik
    Marko Topolnik over 7 years
    Definitely does not matter, unless you're explicitly depending on currentThread. No visibility or ordering issues exist.
  • Sergei Tachenov
    Sergei Tachenov over 7 years
    @Marko, any prooflinks for that? I am kind of confused between your “definitely does not matter” and the docs' “the action may be performed at whatever time and in whatever thread the library chooses”. Is there any explicit guarantee that all operations will even complete by the time the forEach call returns?
  • Marko Topolnik
    Marko Topolnik over 7 years
    Even in the case of parallel stream, all actions will have happened before the return of forEach. In the case of sequential stream, there will be a happens-before ordering between all individual operations because that's the meaning of "sequential".
  • Sergei Tachenov
    Sergei Tachenov over 7 years
    @Marko, the question is, where does it say that? It sounds reasonable, yes, but searching for “happens” through Stream docs yields nothing on that. And what exactly do they mean by “If the action accesses shared state, it is responsible for providing the required synchronization.” if what you're saying is true? (And I'm not saying it isn't.)
  • Marko Topolnik
    Marko Topolnik over 7 years
    I guess no explicit wording is necessary to imply that "sequential" entails that "there is a happens-before between the operations on any two stream elements". About your quote on required synchronization, this pertains to parallel streams only. You may again argue that the wording is such that the qualification "for parallel streams" does not apply to your sentence, but actually it does.
  • Marko Topolnik
    Marko Topolnik over 7 years
    As to the guarantee that all operations have completed when forEach returns, here's the quote: In almost all cases, terminal operations are eager, completing their traversal of the data source and processing of the pipeline before returning. Only the terminal operations iterator() and spliterator() are not
  • Sergei Tachenov
    Sergei Tachenov over 7 years
    @Marko, oh, I see. I've been reading Stream docs, and not java.util.stream docs. Yes, now it's a bit more clear, although it would be even more clear if they explicitly mentioned happens-before relationships there. It would both avoid confusion and allow for efficient searching.
  • Marko Topolnik
    Marko Topolnik over 7 years
    Although it could be more explicit, we must still consider that what i stated is the core part of the definition of "sequential". In fact, my definition is probably even more relaxed than typically assumed. The "obvious" meaning of "sequential" is "totally ordered by program order".
  • Sergei Tachenov
    Sergei Tachenov over 7 years
    @Marko, an interesting point. To me the obvious meaning was totally ordered by the order of the stream's items. Now that I think of it, some streams don't even have a clearly defined order, so your definition of obvious makes more sense.
  • Marko Topolnik
    Marko Topolnik over 7 years
    What I have in mind is the usage of "sequential" in a sentence like When the terminal operation is initiated, the stream pipeline is executed sequentially or in parallel depending on the mode of the stream on which it is invoked. "Sequential execution" is one that is totally ordered by program order, or at the very least by the happens-before order.
  • Nikolas Charalambidis
    Nikolas Charalambidis over 7 years
    Clicked +1 for a nice usage of the extended library. However I prefer to use the default ones :))