使用 Stream.reduce() 减少对象列表
Ruducing a List of objects using Stream.reduce()
我有一个 list 的 objects,我需要将 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 则根本不会执行!
有没有想过使用 groupBy
和 reduce
获得预期的输出?
更新
生成的类型不正确。因为您没有在 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 ?
如果您想将对象列表缩减为具有预定义 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()));
我有一个 list 的 objects,我需要将 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 则根本不会执行!
有没有想过使用 groupBy
和 reduce
获得预期的输出?
更新
生成的类型不正确。因为您没有在 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 ?
如果您想将对象列表缩减为具有预定义 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()));