使用 Stream.reduce() 减少对象列表

Ruducing a List of objects using Stream.reduce()

我有一个 listobjects,我需要将 status 等于我的 [=19] 的对象分组=]到单个定制的 count = sumOfSameObjectsCount .

我们有classMyObject

class MyObject {
   Integer id;
   String name;
   String status;
   Long count;
   //constructor with attributes
   //getters 
   //setters
} 

建议实施:

List<MyObject> resultList = listOfObjects.stream()
    .collect(Collectors.groupingBy(MyObject::getStatus))
    .entrySet().stream()
    .map(e -> e.getValue().stream()
            .reduce((partialResult,nextElem) -> 
                {
                    LOGGER.info("ahaaaa! inside your reduce block ");
                    if(partialResult.getStatus().equals(customizedStatus)) {
          LOGGER.info("equal to my customizedStatus");
                        return new MyObject(customizedId, customizedName, customizedStatus, partialResult.getCount()+nextElem.getCount());
                    } else {
          LOGGER.info("not equal to my customizedStatus");
                        return new MyObject(partialResult.getId(), partialResult.getName(), partialResult.getStatus(), partialResult.getCount());
                    }
                }
            )
        )
    .map(f -> f.get())
    .collect(Collectors.toList());

万一有多个 status 等于我的 customizedStatus 的对象,事情就会像魅力一样运作。
输入:

[
                    {
                        "id": XX,
                        "name": "nameXX",
                        "status": "statusXX",
                        "count": countXX
                    },
                    {
                        "id": YY,
                        "name": "nameYY",
                        "status": "statusYY",
                        "count": countYY
                    },
                    {
                        "id": ZZ,
                        "name": "nameZZ",
                        "status": "customizedStatus",
                        "count": countZZ
                    },
                    {
                        "id": ZZz,
                        "name": "nameZZz",
                        "status": "customizedStatus",
                        "count": countZZz
                    }
                ]

输出:

[
                    {
                        "id": XX,
                        "name": "nameXX",
                        "status": "statusXX",
                        "count": countXX
                    },
                    {
                        "id": YY,
                        "name": "nameYY",
                        "status": "statusYY",
                        "count": countYY
                    },
                    {
                        "id": customizedId,
                        "name": "customizedName",
                        "status": "customizedStatus",
                        "count": countZZ+countZZz
                    }
                ]

如果有一个 status 等于我的 customizedStatus 的对象,也需要对其进行自定义,不幸的是 reduce 块被跳过了!
输入:

[
                    {
                        "id": XX,
                        "name": "nameXX",
                        "status": "statusXX",
                        "count": countXX
                    },
                    {
                        "id": YY,
                        "name": "nameYY",
                        "status": "statusYY",
                        "count": countYY
                    },
                    {
                        "id": ZZ,
                        "name": "nameZZ",
                        "status": "customizedStatus",
                        "count": countZZ
                    }
                ]

输出:

[
                    {
                        "id": XX,
                        "name": "nameXX",
                        "status": "statusXX",
                        "count": countXX
                    },
                    {
                        "id": YY,
                        "name": "nameYY",
                        "status": "statusYY",
                        "count": countYY
                    },
                    {
                        "id": ZZ,
                        "name": "nameZZ",
                        "status": "customizedStatus",
                        "count": countZZ
                    }
                ]

预期输出:

[
                    {
                        "id": XX,
                        "name": "nameXX",
                        "status": "statusXX",
                        "count": countXX
                    },
                    {
                        "id": YY,
                        "name": "nameYY",
                        "status": "statusYY",
                        "count": countYY
                    },
                    {
                        "id": customizedId,
                        "name": "customizedName",
                        "status": "customizedStatus",
                        "count": countZZ
                    }
                ]

如果有多个相同的对象status,似乎会执行 reduce,如果没有 reduce 则根本不会执行!
有没有想过使用 groupByreduce 获得预期的输出?

更新

生成的类型不正确。因为您没有在 reduce() 中提供身份,所以它将 return 一个 Optional<Object>,而不是一个对象。

出于同样的原因(因为您正在使用不期望身份的 reduce() 风格),累加器 不会对 单个元素。引自 documentation:

Performs a reduction on the elements of this stream, using an associative accumulation function, and returns an Optional describing the reduced value, if any. This is equivalent to:

 boolean foundAny = false;
 T result = null;
 for (T element : this stream) {
     if (!foundAny) {
         foundAny = true;
         result = element;
     }
     else
         result = accumulator.apply(result, element);
 }
 return foundAny ? Optional.of(result) : Optional.empty();

第一个遇到的流元素将成为部分结果并且没有更多的元素,它将被可选的按原样包装并return编辑。

一个可能的补救方法是引入 身份:

public static final Integer customizedId = 99;
public static final String customizedName = "customizedName";
public static final String customizedStatus = "customizedStatus";

public static void main(String[] args) {
    List<MyObject> listOfObjects =
        List.of(new MyObject(1, "nameXX", "statusXX", 1L),
                new MyObject(2, "nameYY", "statusYY", 1L),
                new MyObject(3, "nameZZz", "customizedStatus", 3L));
    
    List<MyObject> result =
        listOfObjects.stream()
            .collect(Collectors.groupingBy(MyObject::getStatus))
            .entrySet().stream()
            .map(e -> e.getValue().stream()
                .reduce(getIdentity(e), (partialResult, nextElem) -> accumulate(partialResult, nextElem)) )
            .collect(Collectors.toList());
        
    result.forEach(System.out::println);
}

public static MyObject getIdentity(Map.Entry<String, List<MyObject>> entry) {
        
    return entry.getKey().equals(customizedStatus) ?
            new MyObject(customizedId, customizedName, customizedStatus, 0L) :
            entry.getValue().iterator().next();
}
    
public static MyObject accumulate(MyObject result, MyObject next) {
        
    return result.getStatus().equals(customizedStatus) ?
            new MyObject(customizedId, customizedName, customizedStatus, result.getCount() + next.getCount()) :
            new MyObject(result.getId(), result.getName(), result.getStatus(), result.getCount());
}

输出:

MyObject{id=2, name='nameYY', status='statusYY', count=1}
MyObject{id=1, name='nameXX', status='statusXX', count=1}
MyObject{id=99, name='customizedName', status='customizedStatus', count=3}

你可以玩这个 Online demo

但请记住,尝试将大量条件逻辑放入流中并不是最聪明的想法,因为它变得更难阅读。

下面提供的解决方案是在问题更新之前写的,问题已经弄清楚了。虽然,他们没有针对这个特定问题,但有人可能会从中受益,因此我会保留它们。

将列表缩减为单个对象

Is there any solution to make it pass by reduce even listOfObjects entries are different by status ?

如果您想将对象列表缩减为具有预定义 idnamestatus 的单个对象,则无需创建中间映射Collectors.groupingBy().

如果你想为此使用 reduce() 操作,你可以累加 count,然后基于它创建结果对象:

这就是它的样子(虚拟对象的类型已更改为 MyObject 以避免与 java.lang.Object 混淆):

final Integer customizedId =    // intializing the resulting id
final String customizedName =   // intializing the resulting name
final String customizedStatus = // intializing the resulting status
        
List<MyObject> listOfObjects =  // intializing the source list
    
MyObject resultingObject = listOfObjects.stream()
    .map(MyObject::getCount)
    .reduce(Long::sum)
    .map(count -> new MyObject(customizedId,  customizedName, customizedStatus, 0L))
    .orElseThrow(); // or .orElse(() -> new MyObject(customizedId,  customizedName, customizedStatus, 0L));

实现它的另一种方法是利用 MyObject 是可变的这一事实,并将其用作 collect() 操作中的容器:

MyObject resultingObject = listOfObjects.stream()
    .collect(() -> new MyObject(customizedId,  customizedName, customizedStatus, 0L),
        (MyObject result, MyObject next) -> result.setCount(result.getCount() + next.getCount()),
        (left, right) -> left.setCount(left.getCount() + right.getCount()));