0

I am working on a program that is supposed to describe a takeout system. I have a specific method that is supposed to simulate whether a customer has enough money to buy something using an interface. The problem I encountered was that while I was trying to implement an interface, UserInputRetriever, using lambda..I could only use final variables within the lambda expression. When it got to making a return statement for the method itself, shouldSimulate(), I realized I couldnt because what I returned is only visible in the lambda. Now IntelliJ, suggested using a final array which worked, because I was able to use it within the lambda expression and outside of it. I just need understanding as to why using array worked as compared to using a variable. Can you please explain?

The method is as follows:

public boolean shouldSimulate() {
    boolean[] hasMoney = new boolean[1];

    String userPrompt = """
            Please choose an option from the following:
            0. To end simulation process
            1. To proceed with simulation process""";

    UserInputRetriever<?> intUserInputRetriever = selection -> {

        if(selection == 1 && customer.money >= menu.getLowestFoodCost()){
            System.out.println("You have enough money.\nProceeding with " +
                    "simulation");
            hasMoney[0] = true; 
        }

        else if (selection == 1 && customer.money < menu.getLowestFoodCost()){
            System.out.println("You do not have enough money.\nEnding the" +
                    " simulation");
            hasMoney[0] = false;
        }

        else if(selection == 0){
            System.out.println("Terminating simulation");
            hasMoney[0] = false;
        }

        else
            throw new IllegalArgumentException("Please choose a valid " +
                    "option");

        return hasMoney[0];// if hasMoney was a variable, this would not throw a compile time error
    };
    getOutputOnInput(userPrompt, intUserInputRetriever);// you can ignore this line

    return hasMoney[0];// if hasMoney was a variable, this would throw a compile time error
}

I tried using a variable first locally and tried to also use that in my interface implementation (which used lambda), but it threw errors. But upon using suggestion from IntelliJ which suggested using an array, it worked. I would like to know why

  • 2
    Sounds like you're asking about [Variable used in lambda expression should be final or effectively final](https://stackoverflow.com/q/34865383/6395627) (also see [Why are only final variables accessible in anonymous class?](https://stackoverflow.com/q/4732544/6395627)). – Slaw Jan 05 '23 at 06:24
  • what is `UserInputRetriever`? where it comes from? it should hold the result value. you should not modify any variable outside of the lambda, that is a very bad practice. – Pavel Jan 05 '23 at 06:25
  • Thanks @Slaw Reading the thread you provided shed some light as to the why. I was just wondering why that didnt apply to array values, but only variables – Tobi Cinquante Jan 06 '23 at 07:12
  • @Pavel I am not modifying the value at all. As stated UserInputRetriever is an interface. I simply use the result from that interface implementation to compute another value – Tobi Cinquante Jan 06 '23 at 07:14
  • 1
    Ultimately, because `array[0] = newValue` does not modify the local variable. More specifically, it doesn't reassign the local variable, therefore it is still final or effectively final. The local variable in this case being `array`, which references a specific array instance. Writing to a position in that array is updating that array's _instance_ state (which is not a local variable). You could see the same thing with any mutable object (e.g., `User user = ...; () -> user.setName("foo");`). – Slaw Jan 06 '23 at 07:14
  • It helps to understand the why if you understand how programs are stored in memory, and how the stack and the heap work. A local variable is stored on the stack (the variable itself, not necessarily the object it references). It only exists until the method returns. After that, the memory it was stored at can become anything, even meaningless garbage. But a lambda/anonymous class instance can live beyond the scope of the method. The "workaround" then was to "capture" the value of the local variable and store it in an implicit field of the lambda/anonymous class. [cont.] – Slaw Jan 06 '23 at 07:23
  • @Slaw That is quite interesting. I definitely need to read more on lambdas. T Thanks. That actually makes sense because the elements of the array is what is being modified and not the variable or the reference to the array itself(which is on the stack). I feel really stupid now because the answer is actually very simple and logical. Thanks again! – Tobi Cinquante Jan 06 '23 at 07:24
  • [cont.] But if the local variable could be modified, then that could lead to subtle bugs. Especially if multiple threads are involved. So, they require the local variable to be (effectively) final to prevent such bugs from ever occurring. But fields are stored in the heap. The references to elements of an array are on the heap. These references live beyond any method invocation. This heap state is also covered by the Java Memory Model (JMM), giving well-defined behavior in multi-threaded contexts. – Slaw Jan 06 '23 at 07:26

1 Answers1

2

The Java Lang. Spec. states that the "restriction to effectively final variables prohibits access to dynamically-changing local variables, whose capture would likely introduce concurrency problems".

In order to address this problem you can use atomic references or atomic types, e.g., AtomicBoolean.

BTW: It would have been helpful for others to provide an executable example of the problem and the solution. Something like this makes it easier to help:

import org.junit.jupiter.api.Test;

import java.util.concurrent.atomic.AtomicBoolean;

import static org.junit.jupiter.api.Assertions.*;

public class FunctionalInterfaceProblem {
    public static String EXCEPTION_MESSAGE = "Value not 0 or 1";

    @java.lang.FunctionalInterface
    interface SomeInterfaceWithBoolean<T> {
        boolean calculate(T i) throws IllegalArgumentException;
    }

    @Test
    public void testWihArray() {
        boolean[] outcome = new boolean[1];

        SomeInterfaceWithBoolean<Integer> result = i -> {
            if(i == 1){
                outcome[0] = true;
            } else if (i == 0){
                outcome[0] = false;
            } else {
                throw new IllegalArgumentException(EXCEPTION_MESSAGE);
            }
            return outcome[0];
        };

        assertFalse(result.calculate(0));
        assertTrue(result.calculate(1));
        Throwable exception = assertThrows(IllegalArgumentException.class, ()->{result.calculate(2);} );
        assertEquals(EXCEPTION_MESSAGE, exception.getMessage());
    }

    @java.lang.FunctionalInterface
    interface SomeInterfaceWithAtomicBoolean<T> {
        AtomicBoolean calculate(T i) throws IllegalArgumentException;
    }

    @Test
    public void testWihAtomicBoolean() {
        AtomicBoolean outcome = new AtomicBoolean(false);

        SomeInterfaceWithAtomicBoolean<Integer> result = i -> {
            if(i == 1){
                outcome.set(true);
            } else if (i == 0){
                outcome.set(false);
            } else {
                throw new IllegalArgumentException(EXCEPTION_MESSAGE);
            }
            return outcome;
        };

        assertFalse(result.calculate(0).get());
        assertTrue(result.calculate(1).get());
        Throwable exception = assertThrows(IllegalArgumentException.class, ()->{result.calculate(2);} );
        assertEquals(EXCEPTION_MESSAGE, exception.getMessage());
    }

}
Roland J.
  • 76
  • 4
  • Thanks for the explanation, Roland! It was really clear! I will also make sure to provide an executable of the problem next time. For everyone else who reads, I apologize for not putting enough details. First time using Stack Overflow – Tobi Cinquante Jan 06 '23 at 07:18