修改forEach
中的局部变量会产生编译错误:
正常
int ordinal = 0;
for (Example s : list) {
s.setOrdinal(ordinal);
ordinal++;
}
使用Lambda
int ordinal = 0;
list.forEach(s -> {
s.setOrdinal(ordinal);
ordinal++;
});
知道如何解决这个问题吗?
答案 0 :(得分:97)
任何类型的包装都很好。
使用 Java 8 + ,使用AtomicInteger
:
AtomicInteger ordinal = new AtomicInteger(0);
list.forEach(s -> {
s.setOrdinal(ordinal.getAndIncrement());
});
......或数组:
int[] ordinal = { 0 };
list.forEach(s -> {
s.setOrdinal(ordinal[0]++);
});
使用 Java 10 + :
var wrapper = new Object(){ int ordinal = 0; };
list.forEach(s -> {
s.setOrdinal(wrapper.ordinal++);
});
注意:如果您使用并行流,请务必小心。您可能不会得到预期的结果。像斯图尔特这样的其他解决方案可能更适合这些情况。
int
当然,这对int
以外的类型仍然有效。您只需将包装类型更改为AtomicReference
或该类型的数组。例如,如果您使用String
,请执行以下操作:
AtomicReference<String> value = new AtomicReference<>();
list.forEach(s -> {
value.set("blah");
});
使用数组:
String[] value = { null };
list.forEach(s-> {
value[0] = "blah";
});
或者使用Java 10 +:
var wrapper = new Object(){ String value; }
list.forEach(s->{
wrapper.value = "blah";
});
答案 1 :(得分:12)
这非常接近XY problem。也就是说,被问到的问题本质上是如何从lambda变异捕获的局部变量。但是手头的实际任务是如何对列表中的元素进行编号。
根据我的经验,在80%的时间内存在如何在lambda中改变被捕获的本地的问题,这是一个更好的方法。通常这涉及减少,但在这种情况下,在列表索引上运行流的技术很适用:
IntStream.range(0, list.size())
.forEach(i -> list.get(i).setOrdinal(i));
答案 2 :(得分:9)
如果你只需要将值从外部传递给lambda,而不是把它取出来,你可以使用常规的匿名类而不是lambda来实现:
list.forEach(new Consumer<Example>() {
int ordinal = 0;
public void accept(Example s) {
s.setOrdinal(ordinal);
ordinal++;
}
});
答案 3 :(得分:7)
由于lamda外部使用的变量必须(隐式)为final,因此必须使用类似AtomicInteger
的内容或编写自己的数据结构。
请参阅 https://docs.oracle.com/javase/tutorial/java/javaOO/lambdaexpressions.html#accessing-local-variables
答案 4 :(得分:3)
AtomicInteger
(或任何其他能够存储值的对象)的替代方法是使用数组:
final int ordinal[] = new int[] { 0 };
list.forEach ( s -> s.setOrdinal ( ordinal[ 0 ]++ ) );
但请参阅the Stuart's answer:可能有更好的方法来处理您的案件。
答案 5 :(得分:2)
如果您使用的是Java 10,则可以使用var
:
var ordinal = new Object() { int value; };
list.forEach(s -> {
s.setOrdinal(ordinal.value);
ordinal.value++;
});
答案 6 :(得分:2)
我知道这是一个老问题,但是如果您对变通方法感到满意,考虑到外部变量应该是最终变量,您可以简单地这样做:
final int[] ordinal = new int[1];
list.forEach(s -> {
s.setOrdinal(ordinal[0]);
ordinal[0]++;
});
也许不是最优雅的,甚至不是最正确的,但它会做到的。
答案 7 :(得分:1)
是的,您可以从lambda内部修改局部变量(以其他答案所示的方式),但是您不应该这样做。 Lambda已针对函数式编程进行了编写,这意味着:无副作用。您想做的事被认为是不好的风格。在并行流的情况下也很危险。
您应该找到没有副作用的解决方案,或者使用传统的for循环。
答案 8 :(得分:0)
您可以将其包装起来以解决编译器问题,但请记住,不鼓励使用lambdas中的副作用。
引用javadoc
通常不鼓励行为参数对流操作的副作用,因为它们通常会导致无意中违反无国籍要求 少量的流操作,例如forEach()和peek(),只能通过副作用运行;这些应该小心使用
答案 9 :(得分:0)
我的问题略有不同。我不需要在forEach中增加局部变量,而是需要将一个对象分配给局部变量。
我通过定义一个私有内部域类来解决这个问题,该类包含了我想迭代的列表(countryList)和我希望从该列表中获取的输出(foundCountry)。然后使用Java 8&#34; forEach&#34;,我迭代列表字段,当找到我想要的对象时,我将该对象分配给输出字段。因此,这会为局部变量的字段赋值,而不会更改局部变量本身。我相信由于局部变量本身没有改变,编译器不会抱怨。然后,我可以使用在列表外部的输出字段中捕获的值。
域对象:
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());
你可以删除包装类方法&#34; setCountryList()&#34;并制作字段&#34; countryList&#34;最后,但我没有得到编译错误,这些细节保持原样。
答案 10 :(得分:0)
要获得更通用的解决方案,您可以编写一个通用的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++;
});
(这是Almir Campos提供的解决方案的一种变体)。
在特定情况下,这不是一个好的解决方案,因为出于您的目的,Integer
比int
差,我认为这种解决方案更笼统。