问题的历史:
在Java 8之前,若要在内部类或匿名类中使用外部作用域的变量,这些变量必须被声明为final。在Java 8中,这一要求得到了放宽:现在,如果变量在实际使用中没有被修改(effectively final),则可以不显式声明为final。
问题:
Lambda表达式和内部类使用对外部块中变量的闭包。然而,如果这些变量的值被修改,就会引起困惑和不正确的行为——无法判断使用哪个值。
解决方案:
编译器仅允许在内部类或lambda中使用一个变量,如果它是effectively final——即在初始化后没有被修改,即使没有显式声明为final。
代码示例:
public void demo() { int x = 10; Runnable r = () -> System.out.println(x); // x — effectively final r.run(); }
如果试图修改x:
public void demo() { int x = 10; x = 20; // 现在x不是有效的final Runnable r = () -> System.out.println(x); // 编译错误 }
关键特性:
如果引用本身是effectively final,可以使用可变对象吗?
可以,如果引用不变,但引用的对象可以改变——这是允许的。例如,
List<String> list = new ArrayList<>(); list.add("A"); Runnable r = () -> System.out.println(list.get(0)); // OK list = new ArrayList<>(); // 如果是这样,会有编译错误
可以将变量声明为final,然后仍然修改对象的内容吗?
可以。final与引用相关,而与对象的内容无关。通过引用修改对象状态是允许的。例如,
final List<Integer> nums = new ArrayList<>(); nums.add(5); // OK nums = new ArrayList<>(); // 错误
可以在lambda中使用方法参数变量吗?
可以,如果这些参数也是effectively final——即在初始化后没有在方法内部被修改。
在方法中使用变量,这些变量在创建lambda后意外被重写,导致程序无法编译,并浪费时间查找原因。
优点:
缺点:
开发人员通过AtomicInteger(或其他holder对象)使用可增值的变量,这个对象存储的是引用而不是值,从而确保即使在lambda表达式中需要修改计数器时,lambda的正常工作。
优点:
缺点: