可变对象上的流上Java reduce

可变对象上的流上Java reduce,java,java-stream,immutability,Java,Java Stream,Immutability,在阅读了OP说应该对不可变对象执行reduce操作的地方之后,下面的用法是否错误?如果是,为什么?它生成我所期望的结果,即结果“foo”对象中的数字4 @Test public void foo() { List<Foo> foos = new ArrayList<>(); foos.add(new Foo(1)); foos.add(new Foo(1)); foos.add(new Foo(1)); foos.add(ne

在阅读了OP说应该对不可变对象执行reduce操作的地方之后,下面的用法是否错误?如果是,为什么?它生成我所期望的结果,即结果“foo”对象中的数字4

   @Test
public void foo() {
    List<Foo> foos = new ArrayList<>();
    foos.add(new Foo(1));
    foos.add(new Foo(1));
    foos.add(new Foo(1));
    foos.add(new Foo(1));
    Foo foo = foos.stream().reduce(new Foo(0), Foo::merge);

    System.out.println();
}

static class Foo {
    int foo;
    Foo(int f) {
        foo = f;
    }

    Foo merge(Foo other) {
        foo += other.foo;
        return this;
    }
}
@测试
公共图书馆{
List foos=new ArrayList();
添加(新的Foo(1));
添加(新的Foo(1));
添加(新的Foo(1));
添加(新的Foo(1));
Foo-Foo=foos.stream().reduce(新的Foo(0),Foo::merge);
System.out.println();
}
静态类Foo{
int foo;
Foo(内部文件){
foo=f;
}
Foo合并(Foo其他){
foo+=other.foo;
归还这个;
}
}

考虑以下几点。整数是不可变的,Foo是可变的。 分别创建两个列表

List<Foo> foos = IntStream.range(1, 1001).mapToObj(Foo::new)
        .collect(Collectors.toList());
List<Integer> ints = IntStream.range(1,1001).boxed()
        .collect(Collectors.toList());
印刷品

500500
500500
570026
500500
两者都是正确的

现在通过并行流使用线程再次减少它们,使用第三个参数组合不同的线程以减少

Foo foo = foos.parallelStream().reduce(new Foo(0), Foo::merge, Foo::merge);
Integer integer = ints.parallelStream().reduce(Integer.valueOf(0), (a,b)->a+b, (a,b)->a+b);

System.out.println(foo);
System.out.println(integer);
印刷品

500500
500500
570026
500500
哎呀!这个问题与多个线程和foo对象在没有任何适当同步的情况下同时更新有关

如果将
Foo
类合并方法修改为以下内容,则一切正常

Foo merge(Foo other) {
   return new Foo(this.foo + other.foo);
}
因此,Foo仍然可以通过setter进行修改,但您不应该在缩减操作中使用setter。始终返回新实例,而不是修改当前实例


class Foo {
        int foo;
        
        Foo(int f) {
            foo = f;
        }
        
        Foo merge(Foo other) {
            foo+=other.foo;
            return new Foo(foo);
        }
        public String toString() {
            return foo + "";
        }
    }
}

由于有一种趋势,开发人员会回答“但如果我不使用并行”,所以值得注意的是,这种错误使用的问题并不局限于并行处理。例如,
Map m=IntStream.range(11001).mapToObj(Foo::new).collect(Collectors.groupby(f->f.Foo%8,Collectors.reduced(new Foo(0),Foo::merge));System.out.println(m.values().stream().mapToInt(f->f.foo.sum())
此外,还可以使用
collect
,而不是使用返回新对象的合并函数。例如,
Foo-Foo=foos.parallelStream().collect(()->new-Foo(0),Foo::merge,Foo::merge)使用原始的可变类可以完美地工作…非常有趣,我认为只有当它并行运行时,问题才会出现。你能详细解释一下这个地图的例子吗?为什么它不能像一些人所期望的那样工作?@Holger可以给我一个提示,在分组过程中,是什么导致了这种奇怪的行为,谢谢?@fishysushi
reduce
的第一个参数是可以在任意位置合并的标识元素,任意次数,因为合同要求这没有任何区别。当你修改那个对象时,你就是在破坏契约。在实践中,这表明当在不同的工作线程中执行多个并行缩减时,或者当由于分组而执行多个缩减时,每个组一个。可以通过并行分组最大化。未来可能会带来其他业务,其中多次或部分削减是有益的。