-2

I am wondering why I can use the method parameters i and j inside the lambda and where they are stored in the anonymous class. I also noticed the compiler automatically makes these parameters final. What's going on here in the background I am not aware of?

public class Editor {

    public interface Task {
        int edit();
    }

    static ArrayList<Task> tasks = new ArrayList<>();

    public static void add(int i, int j) {
        tasks.add(() -> i + j);
    }


    public static void main(String[] args) {
        add(4,5);
        System.out.println(tasks.get(0).edit());
    }

}
Olivier
  • 13,283
  • 1
  • 8
  • 24
Dani
  • 49
  • 6

2 Answers2

1

I also noticed the compiler automatically makes these parameters final.

Actually that is not correct.

What happens is that the compiler determines that the parameters are effectively final at the point in the code where the lambda expression is created. Effectively final means that the code (as written) does not change the values.

See JLS 15.27.2 for the precise wording.

Since they are effectively final in your example, the compiler allows you to use i and j in the lambda. (If they weren't, you would get a compilation error!)

What actually happens is that the compiler (or more precisely, the compiler writer) reasons that since i and j cannot change, the compiled bytecodes can save the i and j values in synthetic fields of the object that represents the lambda. Later, when the lambda is called, it gets the values of i and j from those fields. That way, the compiled code doesn't need to worry that the stack frame containing the original i and j variables may have gone away.

Stephen C
  • 698,415
  • 94
  • 811
  • 1,216
0

You can use i and j, because effectively they are being instance variables.

You can think of your lambda as a much shorter equivalent of this implementation:

public class MyTask implements Task {

        private final int i;
        private final int j;

        public MyTask(int i, int j) {
            this.i = i;
            this.j = j;
        }

        @Override
        public int edit() {
            return this.i + this.j;
        }
}

Then instead of using lambda, you add you task to the list like so:

tasks.add(new MyTask(i, j));

As for second question, i believe this SO question should be good read on the topic. Also this one.

Chaosfire
  • 4,818
  • 4
  • 8
  • 23
  • 1
    they are not made `final` for making the lambda stateless. If not declared `final`, they must be effectively final (as explained), but if changed, even inside the lambda, they are not effectively final anymore - so they cannot be changed inside the lambda! (if changed you should not get an error stating they are final, but that they must be final or effectively final) – user16320675 Apr 09 '22 at 09:40
  • @user16320675 I am not sure i can agree with you. Your explanation of how `effectively final` works is certainly **correct**, but it's like saying they must be final, because somebody said so. You never try to explain why this somebody decided so. It's explained in detail, in the questions i linked above, why this somebody decided on this restriction. If i misunderstood you, please feel free to corect me. – Chaosfire Apr 09 '22 at 10:55
  • 1
    Sure I did not explain why it was decided that it must be final, why should I? I cannot really knew it, I was not involved in that decision (I have some idea and I read a bit, I know some *possible* reasons, but that is just guessing), also there are tons of things I did not try to explain... My point was that "As for why they are being made final, ideally lambda functions should be stateless" is not the correct (IMHO) - as I tried to explain, they are not being made final! – user16320675 Apr 09 '22 at 14:15
  • I guess my poor choice of wording made it sound as if that's the one and only reason, which is definitely not true. Still, your explanation is leaving me unconvinced(maybe i am interpreting it the wrong way...), so let's agree to disagree for now :) – Chaosfire Apr 10 '22 at 06:56
  • 1
    They are not "being made final". They are required to be *effectively final*. And they are not being required to be effectively final "because someone said so". It is because the JLS said so! Read the relevant part of the JLS: [15.27.2](https://docs.oracle.com/javase/specs/jls/se17/html/jls-15.html#jls-15.27.2). *"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."* – Stephen C Apr 10 '22 at 07:48
  • @user16320675 Well, considering i don't seem to have problem interpreting stephen's comment and answer, i highly doubt this communication problem stems from the correct choice of my username(how exactly is that relevant to the discussion?)... Anyway, i've already editted the controversial part(yes, you were right, and i was wrong), so this discussion itself is no longer relevant. I wish you happy coding :) And StephenC, thanks for clarifying things up. – Chaosfire Apr 10 '22 at 10:00