Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Isn't effectively final/final restriction useless? [duplicate]

Tags:

java

jvm

I can't understand why the implementers of the java language made it so that the variable used in a lambda and passed there from a function scope has to be final.

I decompiled this code:

public class Main {
    @FunctionalInterface
    interface Test {
        void method(int t);
    }

    static void test(Test t) {
        t.method(3);
    }

    public static void main(String... args) {
        int a = 3;
        test((i)-> {
            System.out.println(a + i);
        });
    }
}

and what the compiler does is copy that variable as if it was passed through a constructor. I got these 3 classes:

1:

//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//

import Main.1;
import Main.Test;

public class Main {
    public Main() {
    }

    static void test(Test var0) {
        var0.method(3);
    }

    public static void main(String... var0) {
        byte var1 = 3;
        test(new 1(var1));
    }
}

2:

//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//

import Main.Test;

final class Main$1 implements Test {
    Main$1(int var1) {
        this.val$a = var1;
    }

    public void method(int var1) {
        System.out.println(this.val$a + var1);
    }
}

3:

//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//

@FunctionalInterface
interface Main$Test {
    void method(int var1);
}

Why couldn't the implementers just copy the variable regardless of whether it was modified or not so we could do this:

public class Main {
    @FunctionalInterface
    interface Test {
        void method(int t);
    }

    static void test(Test t) {
        t.method(3);
    }

    public static void main(String... args) {
        int a = 3;
        test((i)-> {
            a += 1; // allow modification, a is copied anyway, why not?
            System.out.println(a + i);
        });
    }
}
like image 865
Coder-Man Avatar asked Jan 02 '23 08:01

Coder-Man


1 Answers

There's no technical reason for this. It's just that if you allow non-final fields to be used in a lambda then you could write code that looks fine, but actually doesn't work.

For example:

void printSum(Collection<Integer> numbers) {
  int sum = 0;
  numbers.forEach(i -> sum += i);
  System.out.println(sum);
}

Currently the compiler won't let you do that, since you can't access the non-final sum inside the lambda. As you noted the variable gets copied into the lambda anyway, so it could allow that.

If it did, then this code would compile, but always print 0, since only the sum-copy inside the lambda is modified and not the "real one".

Allowing only "effectively final" variables to be referenced is a good compromise of not requiring the final keyword everywhere while still avoiding misunderstandings like that.

like image 182
Joachim Sauer Avatar answered Feb 05 '23 04:02

Joachim Sauer