我用java stream reduce编写了以下代码示例:
Person reducedPerson = Person.getPersons().stream()
.parallel() //will return surprising result
.reduce(new Person(), (intermediateResult, p2) -> {
intermediateResult.setAge(intermediateResult.getAge() + p2.getAge());
return intermediateResult;
},
(ir1, ir2) -> {
ir1.setAge(ir1.getAge() + ir2.getAge());
return ir1;
});
System.out.println(reducedPerson);
模型:
public class Person {
String name;
Integer age;
public Person() {
age = 0;
name = "default";
}
//...
public Person(String name, Integer age) {
this.name = name;
this.age = age;
}
public static Collection<Person> getPersons() {
List<Person> persons = new ArrayList<>();
persons.add(new Person("Vasya", 12));
persons.add(new Person("Petya", 32));
persons.add(new Person("Serj", 10));
persons.add(new Person("Onotole", 18));
return persons;
}
}
每个代码示例执行返回不同的结果:
例如:
Person{name='default', age=256}
或
Person{name='default', age=248}
我已在combiner
内找到了该问题,因为后续流代码正确执行。
请帮助纠正合并器。
P.S。
预期结果:姓名为“默认”且年龄为72岁的人(列表中所有患者的总和)
与Integer相同的代码,因为reduce结果可以正常工作:
Integer age = Person.getPersons().stream()
.parallel()
.reduce(0, (intermediateResult, p2) -> {
intermediateResult = intermediateResult + p2.getAge();
return intermediateResult;
}, (ir1, ir2) -> {
System.out.println("combiner");
ir1 = ir1 + ir2;
return ir1;
});
System.out.println(age);
答案 0 :(得分:6)
To perform mutable reduction, use collect
:
reducedPerson = Person.getPersons().parallelStream()
.collect(
Person::new,
(p, q) -> p.setAge(p.getAge() + q.getAge()),
(p, q) -> p.setAge(p.getAge() + q.getAge())
);
collect
is specifically designed to accumulate into mutable containers safely even in parallel.
答案 1 :(得分:2)
正如鲍里斯所指出的,问题是流中的突变。
大多数流操作接受描述用户指定的参数 行为,例如lambda表达式w - &gt; w.getWeight()传递给 上面的例子中的mapToInt。为了保持正确的行为,这些 行为参数:
- 必须是非干扰的(它们不会修改流源);并在
- 大多数情况必须是无国籍的(他们的结果不应该依赖于任何情况 在执行流管道期间可能会更改的状态。)
https://docs.oracle.com/javase/8/docs/api/java/util/stream/Stream.html
这是使用reduce的版本,以及使用maptoint和sum的更直接的版本。
class gstackoverflow{
public static void main(String... args) {
Person reducedPerson = Person.getPersons().stream()
.parallel() //will NOT return surprising result
.reduce(new Person("default",0),
(ir1, ir2) -> //no longer mutates
new Person(String.join(",", ir1.getName(), ir2.getName()), ir1.getAge() + ir2.getAge())
);
System.out.println(reducedPerson);
//here is a clean(er) way to do it:
int totalAge = Person.getPersons().stream()
.parallel() //will NOT return surprising result
.mapToInt(Person::getAge)
.sum();
System.out.println(totalAge);
}
}
class Person {//no longer mutable
public String getName() {
return name;
}
public Integer getAge() {
return age;
}
final String name;
final Integer age;
//no args constructor removed
public Person(String name, Integer age) {
this.name = name;
this.age = age;
}
public static Collection<Person> getPersons() {
List<Person> persons = new ArrayList<>();
persons.add(new Person("Vasya", 12));
persons.add(new Person("Petya", 32));
persons.add(new Person("Serj", 10));
persons.add(new Person("Onotole", 18));
return persons;
}
@Override
public String toString() {
final StringBuilder sb = new StringBuilder("Person{");
sb.append("name='").append(name).append('\'');
sb.append(", age=").append(age);
sb.append('}');
return sb.toString();
}
}