简体   繁体   English

为什么 lambda 表达式中使用的变量应该是最终的或有效的最终

[英]Why variable used in lambda expression should be final or effectively final

This question has been previously asked over here这个问题以前在这里问过
My question regarding why which was answered over here我的问题是为什么在这里得到了回答
But I have some doubts in the answer.但我对答案有些怀疑。 The answer provided mentions-提供的答案提到-

Although other answers prove the requirement, they don't explain why the requirement exists.虽然其他答案证明的要求,他们没有解释为什么要求存在。

The JLS mentions why in §15.27.2 : JLS 在§15.27.2 中提到了原因:

The restriction to effectively final variables prohibits access to dynamically-changing local variables, whose capture would likely introduce concurrency problems.对有效最终变量的限制禁止访问动态变化的局部变量,其捕获可能会引入并发问题。

To lower risk of bugs, they decided to ensure captured variables are never mutated.为了降低出现错误的风险,他们决定确保捕获的变量永远不会发生变异。 I am confused by the statement that it would lead to concurrency problems.我对它会导致并发问题的说法感到困惑。

I read the article about concurrency problems on Baeldung but still I am a bit confused about how it will cause concurency problems, can anybody help me out with an example.我在Baeldung上阅读了关于并发问题的文章,但我仍然对它如何导致并发问题感到有些困惑,谁能帮我举个例子。 Thanks in advance.提前致谢。

When an instance of a lambda expression is created, any variables in the enclosing scope that it refers are copied into it.创建 lambda 表达式的实例时,它引用的封闭作用域中的任何变量都会被复制到其中。 Now, suppose if that were allowed to modify, and now you are working with a stale value which is there in that copy.现在,假设是否允许修改,现在您正在使用该副本中存在的陈旧值。 On the other hand, suppose the copy is modified inside the lambda, and still the value in the enclosing scope is not updated, leaving an inconsistency.另一方面,假设在 lambda 内部修改了副本,但封闭范围中的值仍未更新,从而导致不一致。 Thus, to prevent such occurrences, the language designers have imposed this restriction.因此,为了防止这种情况发生,语言设计者强加了这种限制。 It would probably have made their life easier too.这可能也会让他们的生活更轻松。 A related answer for an anonymous inner class can be found here .可以在此处找到匿名内部类的相关答案。

Another point is that you will be able to pass the lambda expression around and if it is escaped and a different thread executes it, while current thread is updating the same local variable, then there will be some concurrency issues too.另一点是,您将能够传递 lambda 表达式,如果它被转义并且不同的线程执行它,而当前线程正在更新相同的局部变量,那么也会存在一些并发问题。

It is for the same reason the anonymous classes require the variables used in their coming out from the scope of themselves must be read-only -> final .出于同样的原因,匿名类要求用于从它们自身作用域出来的变量必须是只读的 -> final

final int finalInt = 0;
int effectivelyFinalInt = 0;
int brokenInt = 0;
brokenInt = 0;

Supplier<Integer> supplier = new Supplier<Integer>() {
    @Override
    public Integer get() {
        return finalInt;                        // compiles
        return effectivelyFinalInt;             // compiles
        return brokenInt;                       // doesn't compile
    }
};

Lambda expressions are only shortcuts for instances implementing the interface with only one abstract method ( @FunctionalInterface ). Lambda 表达式只是使用一个抽象方法 ( @FunctionalInterface ) 实现接口的实例的快捷方式。

Supplier<Integer> supplier = () -> brokenInt;   // compiles
Supplier<Integer> supplier = () -> brokenInt;   // compiles
Supplier<Integer> supplier = () -> brokenInt;   // doesn't compile

I struggle to read the Java Language specification to provide support to my statements below, however, they are logical:我努力阅读Java 语言规范以支持我下面的陈述,但是,它们是合乎逻辑的:

  • Note that evaluation of a lambda expression produces an instance of a functional interface.请注意,对 lambda 表达式的求值会生成函数接口的实例。

  • Note that instantiating an interface requires implementing all its abstract methods.请注意,实例化接口需要实现其所有抽象方法。 Doing as an expression produces an anonymous class.做为一个表达式会产生一个匿名类。

  • Note that an anonymous class is always an inner class.请注意,匿名类始终是内部类。

  • Each inner class can access only final or effectively-final variables outside of its scope: Accessing Members of an Enclosing Class每个内部类只能访问其作用域之外的 final 或有效 final 变量: 访问封闭类的成员

    In addition, a local class has access to local variables.此外,局部类可以访问局部变量。 However, a local class can only access local variables that are declared final.但是,局部类只能访问声明为 final 的局部变量。 When a local class accesses a local variable or parameter of the enclosing block, it captures that variable or parameter.当局部类访问封闭块的局部变量或参数时,它会捕获该变量或参数。

I'd like to preface this answer by saying what I show below is not actually how lambdas are implemented.我想在这个答案的开头说我在下面展示的实际上并不是 lambdas 的实现方式。 The actual implementation involves java.lang.invoke.LambdaMetafactory if I'm not mistaken.如果我没记错的话,实际的实现涉及java.lang.invoke.LambdaMetafactory My answer makes use of some inaccuracies to better demonstrate the point .我的回答利用了一些不准确的地方来更好地说明这一点


Let's say you have the following:假设您有以下内容:

public static void main(String[] args) {
  String foo = "Hello, World!";
  Runnable r = () -> System.out.println(foo);
  r.run();
}

Remember that a lambda expression is shorthand for declaring an implementation of a functional interface.请记住,lambda 表达式是声明函数式接口实现的简写。 The lambda body is the implementation of the single abstract method of said functional interface. lambda 主体是所述功能接口的单个​​抽象方法的实现。 At run-time an actual object is created.在运行时创建一个实际对象。 So the above results in an object whose class implements Runnable .所以上面的结果是一个对象,它的类实现了Runnable

Now, the above lambda body references a local variable from the enclosing method.现在,上面的 lambda 体从封闭方法中引用了一个局部变量。 The instance created as a result of the lambda expression "captures" the value of that local variable.作为 lambda 表达式的结果创建的实例“捕获”了该局部变量的值。 It's almost (but not really) like you have the following:几乎(但不是真的)就像您拥有以下内容:

public static void main(String[] args) {
  String foo = "Hello, World!";

  final class GeneratedClass implements Runnable {
    
    private final String generatedField;

    private GeneratedClass(String generatedParam) {
      generatedField = generatedParam;
    }

    @Override
    public void run() {
      System.out.println(generatedField);
    }
  }

  Runnable r = new GeneratedClass(foo);
  r.run();
}

And now it should be easier to see the problems with supporting concurrency here:现在应该更容易在这里看到支持并发的问题:

  1. Local variables are not considered "shared variables".局部变量不被视为“共享变量”。 This is stated in §17.4.1 of the Java Language Specification :Java 语言规范的§17.4.1 中有说明:

    Memory that can be shared between threads is called shared memory or heap memory.可以在线程之间共享的内存称为共享内存或堆内存。

    All instance fields, static fields, and array elements are stored in heap memory.所有实例字段、静态字段和数组元素都存储在堆内存中。 In this chapter, we use the term variable to refer to both fields and array elements.在本章中,我们使用术语变量来指代字段和数组元素。

    Local variables (§14.4), formal method parameters (§8.4.1), and exception handler parameters (§14.20) are never shared between threads and are unaffected by the memory model.局部变量(第 14.4 节)、形式方法参数(第 8.4.1 节)和异常处理程序参数(第 14.20 节)永远不会在线程之间共享,并且不受内存模型的影响。

    In other words, local variables are not covered by the concurrency rules of Java and cannot be shared between threads.也就是说,局部变量不受Java并发规则的约束,不能在线程间共享。

  2. At a source code level you only have access to the local variable.在源代码级别,您只能访问局部变量。 You don't see the generated field.您看不到生成的字段。

I suppose Java could be designed so that modifying the local variable inside the lambda body only writes to the generated field, and modifying the local variable outside the lambda body only writes to the local variable.我想 Java 可以被设计成修改 lambda 主体内的局部变量只写入生成的字段,而修改 lambda 主体外的局部变量只写入局部变量。 But as you can probably imagine that'd be confusing and counterintuitive.但正如您可能想象的那样,这会令人困惑和违反直觉。 You'd have two variables that appear to be one variable based on the source code.根据源代码,您将有两个变量似乎是一个变量。 And what's worse those two variables can diverge in value.更糟糕的是,这两个变量的价值可能会有所不同。

The other option is to have no generated field.另一种选择是没有生成的字段。 But consider the following:但请考虑以下几点:

public static void main(String[] args) {
  String foo = "Hello, World!";
  Runnable r = () -> {
    foo = "Goodbye, World!"; // won't compile
    System.out.println(foo);
  }
  new Thread(r).start();
  System.out.println(foo);
}

What is supposed to happen here?这里应该发生什么? If there is no generated field then the local variable is being modified by a second thread.如果没有生成的字段,则本地变量正在被第二个线程修改。 But local variables cannot be shared between threads.但是局部变量不能在线程之间共享。 Thus this approach is not possible, at least not without a likely non-trivial change to Java and the JVM.因此,这种方法是不可能的,至少在没有对 Java 和 JVM 进行可能的非平凡更改的情况下是不可能的。

So, as I understand it, the designers put in the rule that the local variable must be final or effectively final in this context in order to avoid concurrency problems and confusing developers with esoteric problems.因此,据我所知,设计者制定了这样的规则:在这种情况下,局部变量必须是最终的或有效的最终变量,以避免并发问题并使开发人员与深奥的问题混淆。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

相关问题 lambda 表达式中使用的变量应该是最终的或有效的最终的 - Variable used in lambda expression should be final or effectively final 在lambda表达式中使用的变量应该是final还是有效的final? - Variable used in lambda expression should be final or effectively final? lambda 中使用的变量对于字符串追加应该是最终的或有效的最终的 - Variable used in lambda should be final or effectively final for string appending 线程 Conway 的生命游戏给出了“在 lambda 表达式中使用的变量应该是最终的或有效的最终” - Threading Conway's Game of Life gives "variable used in lambda expression should be final or effectively final" 如何修复“lambda表达式中使用的变量应该是最终的”? - How to fix "variable used in lambda expression should be final"? 尝试更新 CompletableFuture 变量但出现错误:从 lambda 表达式引用的局部变量必须是最终的或有效的最终变量 - Trying to update a CompletableFuture variable but get error : local variables referenced from a lambda expression must be final or effectively final 为什么在 lambda 表达式中使用实例字段不需要是最终的或有效的最终字段? - Why don't instance fields need to be final or effectively final to be used in lambda expressions? 从 lambda 表达式引用的局部变量必须是最终的或有效的最终变量 - local variables referenced from a lambda expression must be final or effectively final 问题:“用作尝试资源资源的变量应该是最终的或有效的最终” - Problem with: "Variable used as a try-with-resources resource should be final or effectively final" lambda表达式应该是最终的FOR LOOP - lambda expression should be final FOR LOOP
 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM