[英]Ruducing a List of objects using Stream.reduce()
我有一个对象列表,我需要将status
等于我的customizedStatus
状态的对象分组到一个具有count
= sumOfSameObjectsCount
的自定义对象。
我们有类MyObject
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
状态,也需要对其进行自定义,不幸的是正在跳过减少块!
输入 :
[
{
"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则不会执行!
使用groupBy
和reduce
获得预期输出的任何想法?
结果类型不正确。 因为您没有在reduce()
中提供身份,所以它将返回Optional<Object>
,但不是对象。
出于同样的原因(因为您使用了一种不需要身份的reduce()
),累加器对单个元素没有影响。 文档中的引用:
使用关联累加函数对此流的元素执行归约,并返回描述归约值(如果有)的 Optional。 这相当于:
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();
第一个遇到的流元素将成为部分结果并且没有更多元素,它将被可选的原样包装并返回。
一种可能的补救措施是引入身份:
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}
你可以玩这个在线演示
但是请记住,尝试将大量条件逻辑放入流中并不是最明智的想法,因为它变得更难以阅读。
下面提供的解决方案是在更新问题之前编写的,并且问题已得到澄清。 虽然,他们没有针对这个特定问题,但有人可能会从中受益,因此我会保留它们。
是否有任何解决方案使其通过减少甚至 listOfObjects 条目因状态而异?
如果您想将对象列表缩减为具有预定义id
、 name
和status
的单个对象,则无需使用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()));
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.