-1

I am trying to write a code to find min and max in an array when I write code using basic java I can use normal variable but when I use forEach loop complier forces me use atomic varaible.Why is it necessary to use atomic variable inside a forEach loop I have attached a sample code for reference

class SingleScan{
    public void elementsData(int []data){
        int min=data[0];
        int max=data[1];
        for(int i=0;i<data.length-1;i++){
            if(data[i]<min){
                min=data[I];//Here I am using normal variable
            }else if(data[i]>max){
                max=data[i];//Here I am using normal variable
            }
        }

        AtomicInteger min = new AtomicInteger(data[0]);
        AtomicInteger max = new AtomicInteger(data[1]);
        Arrays.stream(data).forEach(i-> {
            if (i< min.get()) {
                min.set(i);//Here I have to use atomic variable why I can't simply use normal variables
            }
            if(i>max.get()){
                max.set(i);
            }
        } );

        System.out.println("Max element is "+max +" minimum element is "+min);
    }
}

public class MaxMinSingleScan {
    public static void main(String[] args) {
        int [] element=new int[]{5,8,3,9,6,2,10,7,-1,4};
        SingleScan singleScan=new SingleScan();
        singleScan.elementsData(element);
    }
}
  • Please, provide a compilable code. The variables `min` and `max` are defined at two places. – Nikolas Charalambidis Jun 17 '23 at 16:24
  • 4
    it is not about using "atomic variables", but final or effectively final variables - these are needed because they are being used in a Lambda - [JLS 15.27.2. Lambda Body](https://docs.oracle.com/javase/specs/jls/se20/html/jls-15.html#jls-15.27.2-300): "*Any local variable, formal parameter, or exception parameter used but not declared in a lambda expression must either be final or effectively final (§4.12.4), as specified in §6.5.6.1.*" (why not use `IntSummaryStatistics` or even `min()`/`max()` of `IntStream`?) – user16320675 Jun 17 '23 at 16:25
  • Thanks for the reply but can you give me the reason why it only permits final variable to be used inside lambda.Is there some thread safety issue? or any any other issue? And why normal for loop won't need final as both mentioned codes are performing same operation.I am curious about that.An – Siddharth Bhatia Jun 17 '23 at 16:38
  • The link I provided in first comment also explains: "*Similar rules on variable use apply in the body of an inner class (§8.1.3). The restriction to effectively final variables prohibits access to dynamically-changing local variables, whose capture would likely introduce concurrency problems. Compared to the final restriction, it reduces the clerical burden on programmers.*" – user16320675 Jun 17 '23 at 17:02

2 Answers2

0

For reference, you an just use the IntStream#min and IntStream#max methods.

int minimum = Arrays.stream(data).min().getAsInt();
int maximum = Arrays.stream(data).max().getAsInt();
Reilas
  • 3,297
  • 2
  • 4
  • 17
-2

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:

  1. 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.

  2. 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.