Java 从 lambda 内部修改局部变量

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/30026824/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-11 09:01:04  来源:igfitidea点击:

Modifying local variable from inside lambda

javalambdajava-8

提问by Patan

Modifying a local variable in forEachgives a compile error:

修改局部变量会导致forEach编译错误:

Normal

普通的

    int ordinal = 0;
    for (Example s : list) {
        s.setOrdinal(ordinal);
        ordinal++;
    }

With Lambda

使用 Lambda

    int ordinal = 0;
    list.forEach(s -> {
        s.setOrdinal(ordinal);
        ordinal++;
    });

Any idea how to resolve this?

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

采纳答案by Olivier Grégtheitroade

Use a wrapper

使用包装器

Any kind of wrapper is good.

任何类型的包装都很好。

With Java 8+, use either an AtomicInteger:

对于Java 8+,请使用以下任一方式AtomicInteger

AtomicInteger ordinal = new AtomicInteger(0);
list.forEach(s -> {
  s.setOrdinal(ordinal.getAndIncrement());
});

... or an array:

...或数组:

int[] ordinal = { 0 };
list.forEach(s -> {
  s.setOrdinal(ordinal[0]++);
});

With Java 10+:

使用Java 10+

var wrapper = new Object(){ int ordinal = 0; };
list.forEach(s -> {
  s.setOrdinal(wrapper.ordinal++);
});

Note:be very careful if you use a parallel stream. You might not end up with the expected result. Other solutions like Stuart's might be more adapted for those cases.

注意:如果您使用并行流,务必小心。您可能不会得到预期的结果。像 Stuart 的其他解决方案可能更适合这些情况。

For types other than int

对于除 int

Of course, this is still valid for types other than int. You only need to change the wrapping type to an AtomicReferenceor an array of that type. For instance, if you use a String, just do the following:

当然,这对于除int. 您只需要将包装类型更改为该类型的一个AtomicReference或一个数组。例如,如果您使用 a String,只需执行以下操作:

AtomicReference<String> value = new AtomicReference<>();
list.forEach(s -> {
  value.set("blah");
});

Use an array:

使用数组:

String[] value = { null };
list.forEach(s-> {
  value[0] = "blah";
});

Or with Java 10+:

或者使用 Java 10+:

var wrapper = new Object(){ String value; }
list.forEach(s->{
  wrapper.value = "blah";
});

回答by flo

As the used variables from outside the lamda have to be (implicitly) final, you have to use something like AtomicIntegeror write your own data structure.

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

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

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

回答by Stuart Marks

This is fairly close to an XY problem. That is, the question being asked is essentially how to mutate a captured local variable from a lambda. But the actual task at hand is how to number the elements of a list.

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

In my experience, upward of 80% of the time there is a question of how to mutate a captured local from within a lambda, there's a better way to proceed. Usually this involves reduction, but in this case the technique of running a stream over the list indexes applies well:

根据我的经验,在 80% 以上的情况下,存在如何从 lambda 中改变捕获的局部变量的问题,有更好的方法来进行。通常这涉及减少,但在这种情况下,在列表索引上运行流的技术非常适用:

IntStream.range(0, list.size())
         .forEach(i -> list.get(i).setOrdinal(i));

回答by newacct

If you only need to pass the value from the outside into the lambda, and not get it out, you can do it with a regular anonymous class instead of a lambda:

如果您只需要将值从外部传递到 lambda 中,而不是将其取出,则可以使用常规匿名类而不是 lambda:

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

回答by zakmck

An alternative to AtomicInteger(or any other object able to store a value) is to use an array:

替代AtomicInteger(或任何其他能够存储值的对象)是使用数组:

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

But see the Stuart's answer: there might be a better way to deal with your case.

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

回答by codemonkey

You can wrap it up to workaround the compiler but please remember that side effects in lambdas are discouraged.

您可以将其打包以解决编译器问题,但请记住,不鼓励 lambda 中的副作用。

To quote the javadoc

引用javadoc

Side-effects in behavioral parameters to stream operations are, in general, discouraged, as they can often lead to unwitting violations of the statelessness requirement A small number of stream operations, such as forEach() and peek(), can operate only via side-effects; these should be used with care

流操作的行为参数中的副作用通常是不鼓励的,因为它们通常会导致在不知情的情况下违反无状态要求少数流操作,例如 forEach() 和 peek(),只能通过 side 操作- 效果;这些应该小心使用

回答by Steve T

I had a slightly different problem. Instead of incrementing a local variable in the forEach, I needed to assign an object to the local variable.

我有一个稍微不同的问题。我需要为局部变量分配一个对象,而不是在 forEach 中增加局部变量。

I solved this by defining a private inner domain class that wraps both the list I want to iterate over (countryList) and the output I hope to get from that list (foundCountry). Then using Java 8 "forEach", I iterate over the list field, and when the object I want is found, I assign that object to the output field. So this assigns a value to a field of the local variable, not changing the local variable itself. I believe that since the local variable itself is not changed, the compiler doesn't complain. I can then use the value that I captured in the output field, outside of the list.

我通过定义一个私有内部域类来解决这个问题,该类包含我想要迭代的列表 (countryList) 和我希望从该列表中获得的输出 (foundCountry)。然后使用 Java 8“forEach”,我遍历列表字段,当找到我想要的对象时,我将该对象分配给输出字段。所以这给局部变量的一个字段赋值,而不是改变局部变量本身。我相信由于局部变量本身没有改变,编译器不会抱怨。然后,我可以使用我在列表之外的输出字段中捕获的值。

Domain Object:

域对象:

public class Country {

    private int id;
    private String countryName;

    public Country(int id, String countryName){
        this.id = id;
        this.countryName = countryName;
    }

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getCountryName() {
        return countryName;
    }

    public void setCountryName(String countryName) {
        this.countryName = countryName;
    }
}

Wrapper object:

包装对象:

private class CountryFound{
    private final List<Country> countryList;
    private Country foundCountry;
    public CountryFound(List<Country> countryList, Country foundCountry){
        this.countryList = countryList;
        this.foundCountry = foundCountry;
    }
    public List<Country> getCountryList() {
        return countryList;
    }
    public void setCountryList(List<Country> countryList) {
        this.countryList = countryList;
    }
    public Country getFoundCountry() {
        return foundCountry;
    }
    public void setFoundCountry(Country foundCountry) {
        this.foundCountry = foundCountry;
    }
}

Iterate operation:

迭代操作:

int id = 5;
CountryFound countryFound = new CountryFound(countryList, null);
countryFound.getCountryList().forEach(c -> {
    if(c.getId() == id){
        countryFound.setFoundCountry(c);
    }
});
System.out.println("Country found: " + countryFound.getFoundCountry().getCountryName());

You could remove the wrapper class method "setCountryList()" and make the field "countryList" final, but I did not get compilation errors leaving these details as-is.

您可以删除包装类方法“setCountryList()”并使字段“countryList”最终化,但我没有得到编译错误,这些细节保持原样。

回答by ZhekaKozlov

If you are on Java 10, you can use varfor that:

如果您使用的是 Java 10,则可以使用var

var ordinal = new Object() { int value; };
list.forEach(s -> {
    s.setOrdinal(ordinal.value);
    ordinal.value++;
});

回答by Almir Campos

I know that's an old question, but if you are comfortable with a workaround, considering the fact that the external variable should be final, you can simply do this:

我知道这是一个老问题,但是如果您对解决方法感到满意,考虑到外部变量应该是最终变量这一事实,您可以简单地执行以下操作:

final int[] ordinal = new int[1];
list.forEach(s -> {
    s.setOrdinal(ordinal[0]);
    ordinal[0]++;
});

Maybe not the most elegant, or even the most correct, but it will do.

也许不是最优雅的,甚至不是最正确的,但它会做。

回答by luca.vercelli

To have a more general solution, you can write a generic Wrapper class:

要获得更通用的解决方案,您可以编写一个通用的 Wrapper 类:

public static class Wrapper<T> {
    public T obj;
    public Wrapper(T obj) { this.obj = obj; }
}
...
Wrapper<Integer> w = new Wrapper<>(0);
this.forEach(s -> {
    s.setOrdinal(w.obj);
    w.obj++;
});

(this is a variant of the solution given by Almir Campos).

(这是 Almir Campos 给出的解决方案的变体)。

In the specific case this is not a good solution, as Integeris worse than intfor your purpose, anyway this solution is more general I think.

在特定情况下,这不是一个好的解决方案,因为Integerint您的目的更糟糕,无论如何,我认为这个解决方案更通用。