在 forEach中修改局部变量会产生编译错误:
forEach
正常
int ordinal = 0; for (Example s : list) { s.setOrdinal(ordinal); ordinal++; }
和 Lambda 一起
int ordinal = 0; list.forEach(s -> { s.setOrdinal(ordinal); ordinal++; });
有办法解决吗?
由于来自 lamda 外部的已使用变量必须(隐式地)是 final,因此必须使用类似于 AtomicInteger的东西或编写自己的数据结构。
AtomicInteger
Https://docs.oracle.com/javase/tutorial/java/javaoo/lambdaexpressions.html#accessing-local-variables.
任何形式的包装都是好的。
使用 Java10 + ,使用这个结构,因为它非常容易设置:
var wrapper = new Object(){ int ordinal = 0; }; list.forEach(s -> { s.setOrdinal(wrapper.ordinal++); });
对于 Java8 + ,可以使用 AtomicInteger:
AtomicInteger ordinal = new AtomicInteger(0); list.forEach(s -> { s.setOrdinal(ordinal.getAndIncrement()); });
或者数组:
int[] ordinal = { 0 }; list.forEach(s -> { s.setOrdinal(ordinal[0]++); });
注意: 使用并行流时要非常小心。您可能不会得到预期的结果。像 斯图尔特的这样的其他解决方案可能更适合这些情况。
int
当然,这对于 int以外的类型仍然有效。
例如,使用 Java10 + :
var wrapper = new Object(){ String value = ""; }; list.forEach(s->{ wrapper.value += "blah"; });
或者如果你被 爪哇8或者 9卡住了,使用和上面一样的结构,但是使用 AtomicReference..。
AtomicReference
AtomicReference<String> value = new AtomicReference<>(""); list.forEach(s -> { value.set(value.get() + s); });
String[] value = { "" }; list.forEach(s-> { value[0] += s; });
这非常接近 XY 问题。也就是说,被问到的问题本质上是如何从 lambda 变换捕获的局部变量。但是手头的实际任务是如何对列表中的元素进行编号。
根据我的经验,超过80% 的时间都存在一个问题,那就是如何从一个 lambda 内部变异一个被捕获的本地人,有一个更好的方法来继续。通常这涉及到减少,但是在这种情况下,在列表索引上运行流的技术应用得很好:
IntStream.range(0, list.size()) .forEach(i -> list.get(i).setOrdinal(i));
如果只需要从外部将值传递给 lambda,而不需要传递出去,那么可以使用常规的匿名类而不是 lambda:
list.forEach(new Consumer<Example>() { int ordinal = 0; public void accept(Example s) { s.setOrdinal(ordinal); ordinal++; } });
AtomicInteger的一种替代方法是使用数组(或任何其他能够存储值的对象) :
final int ordinal[] = new int[] { 0 }; list.forEach ( s -> s.setOrdinal ( ordinal[ 0 ]++ ) );
但是,看看 斯图尔特的回答: 可能有一个更好的方法来处理你的情况。
您可以将它包装起来以解决编译器的问题,但是请记住,lambdas 中的副作用是不被鼓励的。
引用 Javadoc的话
一般来说,流操作的行为参数中的副作用是不被鼓励的,因为它们常常会在不知情的情况下导致违反无状态要求 少量的流操作,如 forEach ()和 Peak () ,只能通过副作用进行操作; 这些操作应该谨慎使用
我遇到了一个稍微不同的问题。我需要将一个对象赋给局部变量,而不是在 forEach 中递增一个局部变量。
我通过定义一个私有的内部域类来解决这个问题,该类包装了我想要遍历的列表(country List)和我希望从该列表获得的输出(found Country)。然后使用 Java8“ forEach”迭代列表字段,当找到想要的对象时,将该对象分配给输出字段。所以这会给局部变量的一个字段赋值,而不会改变局部变量本身。我相信因为局部变量本身没有改变,编译器不会抱怨。然后可以使用在列表外的输出字段中捕获的值。
域对象:
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; } }
包装对象:
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; } }
重复操作:
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());
您可以删除包装器类方法“ setCountryList ()”并使字段“ country List”成为 final,但是我没有得到编译错误,因此这些细节保持不变。
如果你使用 Java10,你可以使用 var:
var
var ordinal = new Object() { int value; }; list.forEach(s -> { s.setOrdinal(ordinal.value); ordinal.value++; });
要获得更通用的解决方案,可以编写一个通用 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++; });
(这是阿尔米尔坎波斯给出的解决方案的一个变体)。
在特定的情况下,这不是一个好的解决方案,因为 Integer比 int更不适合您的目的,无论如何,这个解决方案是更一般的我认为。
Integer
是的,您可以从 lambdas 内部修改局部变量(按照其他答案显示的方式) ,但是您不应该这样做。Lambdas 是为函数式编程风格而生的,这意味着: 没有副作用。你想做的事情被认为是不好的风格。在平行流的情况下也是危险的。
您要么找到一个没有副作用的解决方案,要么使用传统的 for 循环。