In the code you provided, you need to use AtomicInteger
instead of standard variables (int
) inside the forEach
loop because of the concept of capturing variables in lambda expressions in Java.
In Java, when you use a lambda expression or an anonymous inner class, you can only access variables that are effectively final or declared as final. This means you can't modify the value of a regular variable inside the lambda expression or anonymous inner class.
Lambda expressions are designed to allow the use of variables from the enclosing scope, even if they are not explicitly passed as parameters. However, the Java language specification imposes some restrictions on such variables to prevent potential concurrency and mutability issues.
By requiring the variable to be effectively final or explicitly final, the Java compiler ensures that it is not modified after the lambda expression captures it. This guarantees that the lambda operates on a stable value and avoids any unexpected behavior that could arise from concurrent modifications.
An effectively final variable is not explicitly declared as final but is never reassigned after its initial assignment. It behaves as a constant within the scope of the lambda expression.
In your code, you're using a lambda expression with Arrays.stream(data).forEach(i -> { ... })
. Inside this lambda expression, you want to update the values of min and max, but you can't do that since they are regular variables.
To work around this limitation, you can use AtomicInteger
, which provides a way to update the value within the lambda expression using the set() method. AtomicInteger
is mutable so that you can modify its value even inside a lambda expression or anonymous inner class.
You can also work around this by using an array to store the min and max values and declare them as final int[] min
and final int[] max
.
class SingleScan {
public void elementsData(int[] data) {
final int[] min = {data[0]};
final int[] max = {data[1]};
Arrays.stream(data).forEach(i -> {
if (i < min[0]) {
min[0] = i;
}
if (i > max[0]) {
max[0] = i;
}
});
System.out.println("Max element is " + max[0] + " minimum element is " + min[0]);
}
By using an array
, you effectively create a container to hold a mutable reference to an int value. Even though the array itself is final, the content of the array (i.e., the int value inside) can be modified.
Another way can be to create a wrapper around min
and max
class MinMaxWrapper {
int min;
int max;
public MinMaxWrapper(int initialValue) {
min = initialValue;
max = initialValue;
}
public void updateMin(int newValue) {
if (newValue < min) {
min = newValue;
}
}
public void updateMax(int newValue) {
if (newValue > max) {
max = newValue;
}
}
}
class SingleScan {
public void elementsData(int[] data) {
MinMaxWrapper minMax = new MinMaxWrapper(data[0]);
Arrays.stream(data).forEach(i -> {
minMax.updateMin(i);
minMax.updateMax(i);
});
System.out.println("Max element is " + minMax.max + " minimum element is " + minMax.min);
}
}
The choice of whether to use AtomicInteger, an array (int[]), or a mutable wrapper class depends on your specific needs and the context of your code. Here's a summary to help you decide:
If you require thread safety or need to handle concurrent modifications, use AtomicInteger. It provides atomic operations and synchronization guarantees, making it suitable for multi-threaded scenarios.
If you're working in a single-threaded environment and concurrency is not a concern, you can use either an array (int[]) or a mutable wrapper class. Both options allow you to update values within a lambda expression.
- Arrays (int[]): Simple and lightweight, but accessing values requires array indexing (min[0], max[0]). Suitable when you want to update multiple variables simultaneously or pass values as references.
- Mutable wrapper class: Provides a more object-oriented approach. You can define methods within the wrapper class to update values. Offers cleaner code and encapsulation but introduces additional complexity.
Consider the trade-offs between simplicity, performance, and thread safety in your specific scenario. Choose the approach that best aligns with your requirements and ensures the correctness and maintainability of your code.