【问题标题】:Java lambda - for loop counter is not effectively finalJava lambda - for 循环计数器不是有效的 final
【发布时间】:2014-07-22 17:10:00
【问题描述】:

考虑到 lambda 在 for 循环中的这种情况,我希望计数器 i 实际上是最终的。

编译器抱怨 i 不是最终的,所以我不得不使用 i2。

for (int i = 0; i < x.getBooks().size(); i++){
   //The compiler needs i to be effectively final.
   int i2 = i;
   List<Book> books = bookstore.stream()
                    .filter(c -> c.getAuthors().get(i2).equals("xxx"))
                    .collect(Collectors.toList());
}

所以问题是为什么 i 在 for 循环的范围内不是有效的 final ,这是最简单的解决方法。

【问题讨论】:

  • 你在哪里使用ii2
  • 有效吗?我不确定那是什么意思。您的代码甚至没有在 lambda 中使用 i
  • 为错字道歉 - 现已编辑。
  • 这实际上是 final 的意思 stackoverflow.com/questions/20938095/…
  • IMO 他们应该允许 final int i,这将阻止在 for 构造之外修改 i

标签: java for-loop lambda java-8


【解决方案1】:

正如其他答案所提到的,effectively final 意味着一个变量只能分配一次并且不会被重新分配。在 for 循环中情况并非如此。 effectively final 存在,否则开发人员必须将变量显式标记为 final 才能在 lambda 中使用它。

但是,我回答的原因是一个解决方案,在不重复 i 的情况下编写代码:

IntStream.range (0, x.getBooks().size()).forEach (i -> {
    List<Book> books = bookstore.stream()
                                .filter(c -> c.getAuthors().get(i).equals("xxx"))
                                .collect(Collectors.toList());
});

【讨论】:

    【解决方案2】:

    TLDR:i 不是 final,因为它在 for 循环的每次迭代中都会被修改 (i++)。


    为什么 i 在 for 循环范围内不是有效的 final ?

    for 循环语法是

    for (initialization; termination; increment) {
        statement(s)
    }
    

    在循环的每次迭代之后调用增量表达式。在您的情况下,增量为i++,因此每次迭代后都会修改i

    您可以通过声明 i final 来确认这一点:

    for (final int i = 0; i < x.getBooks().size(); i++) {
    }
    

    你会得到这个编译错误:

    The final local variable i cannot be assigned.
    It must be blank and not using a compound  assignment
    

    这是最简单的解决方法吗?

    for 循环的情况下:是的。

    但您可以使用while 循环,如@dkatzelforeach 所示:

    int i = 0;
    for (Book book: x.getBooks()) {
        int i2 = i;
        ...  
        i++;
    }
    

    【讨论】:

    • 在您的 foreach 示例中,i 的语义已更改。第一次循环时为 0。现在第一次是 1。此外,它不是有效的最终问题存在相同的问题,因此仍然需要i2
    【解决方案3】:

    来自Java Language Specification

    在基本 for 语句 (§14.14.1) 的 ForInit 部分中声明的局部变量的范围包括以下所有内容:

    • 它自己的初始化器
    • for 语句的 ForInit 部分右侧的任何其他声明符
    • for 语句的ExpressionForUpdate 部分
    • 包含的声明

    关于effectively final

    局部变量或方法、构造函数、lambda 或异常 参数实际上是最终的如果它没有被声明为最终的但它 永远不会作为赋值运算符的左操作数出现 (§15.26)或作为前缀或后缀增量的操作数或 减量运算符

    您的i 变量作为后缀自增运算符的操作数出现

    i++
    

    因此它实际上不是最终的,不能被 lambda 捕获。

    【讨论】:

      【解决方案4】:

      您必须记住,for 循环实际上等同于:

      int i=0;
      while(i < x.getBooks().size()){
        //execute your block
        i++;
      }
      

      所以你看到i 只被声明了一次并且被更新,因此不是最终的。

      x.getBooks()Book.getAuthors() 之间的关系有点不清楚,但显然它们的大小相同?如果没有更好的理解,我认为我无法向您展示更好的方法。但这也可能表明你的设计很糟糕。

      【讨论】:

      • 实际的例子在某种程度上是制造出来的,所以不要太担心。主要问题与 for 循环中计数器的范围有关。
      • @Dan 好的,那么也许我的答案的第一部分就足够了?
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2016-09-27
      • 2014-08-17
      • 2020-07-11
      • 1970-01-01
      相关资源
      最近更新 更多