修改lambda内部的局部变量

Pat*_*tan 86 java lambda java-8

修改局部变量forEach会产生编译错误:

正常

    int ordinal = 0;
    for (Example s : list) {
        s.setOrdinal(ordinal);
        ordinal++;
    }
Run Code Online (Sandbox Code Playgroud)

随着Lambda

    int ordinal = 0;
    list.forEach(s -> {
        s.setOrdinal(ordinal);
        ordinal++;
    });
Run Code Online (Sandbox Code Playgroud)

知道如何解决这个问题吗?

Oli*_*ire 135

使用包装器

使用Java 8+:

AtomicInteger ordinal = new AtomicInteger(0);
list.forEach(s -> {
  s.setOrdinal(ordinal.getAndIncrement());
});
Run Code Online (Sandbox Code Playgroud)

使用Java 10+:

int[] ordinal = { 0 };
list.forEach(s -> {
  s.setOrdinal(ordinal[0]++);
});
Run Code Online (Sandbox Code Playgroud)

  • @mrbela 数组通过引用传递。当您传入一个数组时,您实际上是在传入该数组的内存地址。像整数这样的基本类型是按值发送的,这意味着传入值的副本。按值传递,原始值和发送到方法中的副本之间没有关系 - 操作副本对原始值没有任何作用。通过引用传递意味着原始值和发送到方法中的值是相同的 - 在方法中操作它会更改方法外部的值。 (4认同)

Stu*_*rks 12

这非常接近XY问题.也就是说,被问到的问题本质上是如何从lambda变异捕获的局部变量.但是手头的实际任务是如何对列表的元素进行编号.

根据我的经验,在80%的时间内存在如何从lambda内变异捕获的局部的问题,有更好的方法可以继续.通常这涉及到减少,但在这种情况下,在列表索引上运行流的技术很适用:

IntStream.range(0, list.size())
         .forEach(i -> list.get(i).setOrdinal(i));
Run Code Online (Sandbox Code Playgroud)

  • 好的解决方案,但只有`list`是一个`RandomAccess`列表 (3认同)

new*_*cct 11

如果你只需要将值从外部传递给lambda,而不是将它取出,你可以使用常规的匿名类而不是lambda来实现:

list.forEach(new Consumer<Example>() {
    int ordinal = 0;
    public void accept(Example s) {
        s.setOrdinal(ordinal);
        ordinal++;
    }
});
Run Code Online (Sandbox Code Playgroud)

  • ...如果您需要实际读取结果怎么办?结果对于顶层代码不可见。 (2认同)

flo*_*flo 7

由于来自lamda外部的已使用变量必须(隐式)最终,您必须使用类似的东西AtomicInteger或编写自己的数据结构.

请参阅 https://docs.oracle.com/javase/tutorial/java/javaOO/lambdaexpressions.html#accessing-local-variables.


zak*_*mck 5

另一种方法AtomicInteger是使用数组(或任何其他能够存储值的对象):

final int ordinal[] = new int[] { 0 };
list.forEach ( s -> s.setOrdinal ( ordinal[ 0 ]++ ) );
Run Code Online (Sandbox Code Playgroud)

但是请参阅Stuart 的回答:可能有更好的方法来处理您的案例。