Lambda expressions should be used to capture values, not variables . Capturing values prompts to write code without side effects, because the alternative is more difficult.
What is meant by the capture of values in lambda expression?
Lambda expressions should be used to capture values, not variables . Capturing values prompts to write code without side effects, because the alternative is more difficult.
What is meant by the capture of values in lambda expression?
This means that in lambda expressions you should use external (relative to the expression) immutable values, and not external variables, the value and internal state of which may change. By external immutable values, respectively, we mean effectively final local variables and fields of primitive types, as well as effectively final objects, the internal state of which will not change.
This is due to the fact that Streams and lambda expressions were designed on the basis of their multi-threaded use.
The problem with using a variable ( counter ) instead of a value is seen in this example:
private static class Element { private final int value; public Element(int value) { this.value = value; } public int getValue() { return value; } } private static volatile int counter = 0; public static void main(String[] args) { List<Element> list = new ArrayList<>(); for (int i = 0; i < 100 * 1000; i++) { list.add(new Element(1)); } list.parallelStream().forEach(e -> counter += e.getValue()); System.out.println(counter); } To count on the fact that the value of 100000 will be displayed on the screen is not necessary, because there is a race condition . In my test this code could get the correct value only in 299 cases out of 100 thousand.
This is one of the reasons why local variables used in lambda expressions should be effectively final. Code admissibility
int localCounter = 0; list.parallelStream().forEach(e -> localCounter += e.getValue()); Would lead to a race condition for a local variable, which would be a new round of problems in multi-threaded programming in Java. Local variables are considered thread-safe, and Java developers didn’t want to break this principle.
You can "cheat" the compiler in terms of limiting the effectively final value like this:
int[] localCounter = { 0 }; list.parallelStream().forEach(e -> localCounter[0] += e.getValue()); System.out.println(localCounter[0]); So "shoot yourself in the foot" when using a effectively final local variable is still possible. Of course, one should not be surprised that the value will again be considered wrong. In practice, this is definitely not worth doing.
Yes, you can use AtomicInteger :
AtomicInteger atomicInteger = new AtomicInteger(); list.parallelStream().forEach(e -> atomicInteger.addAndGet(e.getValue())); System.out.println(atomicInteger.get()); However, this kills the whole idea of parallelizing the code.
In this case, it is supposed to use a bunch of map and reduce :
int localCounter = list.parallelStream().map(e -> e.getValue()).reduce(0, (a, b) -> a + b); System.out.println(localCounter); The part with map and reduce can be written like this:
.map(Element::getValue).reduce(0, Integer::sum) Read the article by Brian Goetz (author of "Java Concurrency in Practice") here .
However, problems in capturing variables instead of values may arise not only in parallel execution. For example:
private static class Element { public int x; public Element(int x) { this.x = x; } public Function<Integer, Integer> getMapper() { return (e -> e + x); } } public static void main(String[] args) { Element element = new Element(2); List<Integer> list1 = Arrays.asList(10, 20, 30); Function<Integer, Integer> function1 = element.getMapper(); element.x = 4; List<Integer> list2 = Arrays.asList(10, 20, 30); Function<Integer, Integer> function2 = element.getMapper(); list1 = list1.stream().map(function1).collect(Collectors.toList()); list2 = list2.stream().map(function2).collect(Collectors.toList()); System.out.println(list1); System.out.println(list2); } In this code, the variable is captured (not effectively final of the field) x , which instead of the expected output
[12, 22, 32] [14, 24, 34] will be displayed
[14, 24, 34] [14, 24, 34] When capturing the same values:
public Function<Integer, Integer> getMapper() { int n = x; return (e -> e + n); } no such problem / error will occur.
Probably, the author meant that lambda expressions should not be touched by variables, but taken to the input value and give values to the output. For example:
Poorly:
final String string = "string"; class.method(() -> string += "abc"); Good:
class.method((string) -> string += "abc"); Source: https://ru.stackoverflow.com/questions/625153/
All Articles