1

I am new to Java 8 and was trying to rewrite an existing code snippet logic using the Java 8 features.

However I am not sure how to use an existing arrayList defined outside the block to get values from it when it is placed inside the lambda block. It complains that it has to be final or effectively final.

I started with converting the inner traditional for loop and encountered the same issues with a counter variable which I was able to sort using AtomicInteger but am not sure how to do that for arrayList as well as I cannot also define the arrayList inside the lambda block since it has a dependency of an i variable that is present in the outer while loop.

Any help will be much appreciated !!! Thanks in advance.

Below is my code snippet :-


public String somemethod(ArrayList someValues){
        int i=0;
        String status="Failed";
        
        ArrayList someOtherValues = new ArrayList();
    try
    {
    while ( i < (someValues.size()))
        {
            someOtherValues = (ArrayList) someValues.get(i);
            someOtherValues.replaceAll(t -> Objects.isNull(t) ? "" : t); //converting every null to "" within the arrayList someOtherValues
            int count=4;
            AtomicInteger counter=new AtomicInteger(5);
            if(!someOtherValues.get(0).toString().equals(""))
            {
            while ( count < (someOtherValues.size()))
            {
                
                IntStream.range(0, 3).forEach(k -> {
                someObjectClass someObject=new someObjectClass();
                someOtherObjectClass id = new someOtherObjectClass(someOtherValues.get(0).toString(),someOtherValues.get(count).toString()) //Line where the error is
                someObject=new someObjectClass(id);
                 someObject.setId(id);
                 if(someCondition)
                 {
                     try
                     {
                     someObject.setSomething(someValue); 
                     counter.incrementAndGet()
                     }
                 }
                 someObject.setsomeOtherValues1(someOtherValues.get(1).toString());
                 someObject.setsomeOtherValues2(someOtherValues.get(3).toString())
                }
                 count=counter.get();
                 counter.incrementAndGet();

            }
            }
            i++;

    }
    catch(Exception e)
    {
        return status;
    }
             
        
}

Right now where it is pending is it complains that someOtherValues, which is an existing arrayList defined outside the lambda block needs to be final or effectively final in order to fetch elements.

Is it literally not possible to change/optimize the above function into a fewer lines of code using Java 8 streams/lambdas/forEach ?

  • Please provide code that compiles, yours doesn't. But yes if you define the list outside of the loop it's not final so it can't be used in lambdas. Define your list inside the loop. – JP Moresmau May 11 '21 at 11:27
  • The code obviously has a compile time error, since it is not allowing me to proceed as I have to declare the arrayList "someOtherValues" final but that is not possible. I cannot even declare it inside since it is being formed from ```someOtherValues = (ArrayList) someValues.get(i);``` and it is outside of the forEach block inside a while loop if you can see. I am looking for some help who can literally help me write the entire content in a java 8 way without logic being impacted. Thanks!!! – MysteriousCoder May 11 '21 at 11:36
  • Hi. Could you please put the code that cause a error also can you explain what is you expect as result. Sometime you have to rewrite the method with stream rather than converting existing code – JHDev May 11 '21 at 11:39
  • Line where the error is : ``` someOtherObjectClass id = new someOtherObjectClass(someOtherValues.get(0).toString(),someOtherValues.get(count).toString()) //Line where the error is ```. I want this method to be called from somewhere and get a status back which needs to be something other than failed. I cannot proceed because I cannot use an existing arraylist defined outside a lambda forEach block without declaring it final or making it as effectively final – MysteriousCoder May 11 '21 at 11:53
  • **Warning**: you are using *raw types* (in your case `ArrayList`). **Never use raw types**, always provide the necessary type arguments. – MC Emperor May 11 '21 at 13:57
  • Thanks @MCEmperor for pointing that out, I will surely provide the necessary type arguments. – MysteriousCoder May 12 '21 at 06:51
  • Does this answer your question? [Variable used in lambda expression should be final or effectively final](https://stackoverflow.com/questions/34865383/variable-used-in-lambda-expression-should-be-final-or-effectively-final) – Gautham M May 12 '21 at 06:54

1 Answers1

0

As a general rule it is not a good idea to try and change outside variables inside a lambda definition. But since Java's very loose concept of final fields and variables only applies to assigning values, it can still be done. The only thing you cannot do in a lambda expression with variable defined outside is assigning new values. So this does not compile:

List<String> lst = new ArrayList<>();
myLambda = e -> {
  lst = new ArrayList<>(); //this violates the 'final' rule
  lst.add(e);
}

It is however possible to call any method of the outside variable, even if it changes the state of the variable. This will work:

myLambda = e -> {
  lst.add(e);
}

Even though you're still changed the state of the variable lst, this is legal code.

But I strongly advise against it. Lambdas are meant to used in a functional matter and should not change any variables defined elsewhere. It's a better choice to create a list inside the lambda, return it from the lambda and then add it to the outside list.

geanakuch
  • 778
  • 7
  • 13
  • 24