问题
This question is an extension to the post: Java 8 groupingby with returning multiple field.
For the same problem, how do you return a list of Customer
? For example, it should return:
Customer("A",4500,6500)
Customer("B",3000,3500)
Customer("C",4000,4500)
回答1:
@Pankaj Singhal's post is the right idea if you want a Map<String, Customer>
as the result set +1. However, I would extract the merging logic into its own function e.g. in the Customer
class you would have a function as such:
public static Customer merge(Customer first, Customer second) {
first.setTotal(first.getTotal() + second.getTotal());
first.setBalance(first.getBalance() + second.getBalance());
return first;
}
Then the stream query would become:
Map<String, Customer> retObj =
listCust.stream()
.collect(Collectors.toMap(Customer::getName, Function.identity(), Customer::merge));
listCust.stream()
creates a stream object i.e.Stream<Customer>
.- collect performs a mutable reduction operation on the elements of this stream using the provided Collector.
- The result of toMap is the provided collector, the
toMap
method extracts the keysCustomer::getName
and values Function.identity() and if the mapped keys contain duplicates, the merge functionCustomer::merge
is used to resolve collisions.
There are three benefits I see with extracting the merging logic into its own function:
- The code is more compact.
- The code is more readable.
- The complexity of the merging is isolated away from the stream.
if however, your intention is to retrieve a Collection<Customer>
:
Collection<Customer> result = listCust.stream()
.collect(Collectors.toMap(Customer::getName,
Function.identity(),
Customer::merge))
.values();
or List<Customer>
as the result set then all you have to do is call values()
and pass the result of that to the ArrayList
constructor:
List<Customer> result = new ArrayList<>(listCust.stream()
.collect(Collectors.toMap(Customer::getName,
Function.identity(),
Customer::merge))
.values());
Update:
if you don't want to mutate the objects in the source then simply modify the merge
function as follows:
public static Customer merge(Customer first, Customer second) {
Customer customer = new Customer(first.getName(), first.getTotal(), first.getBalance());
customer.setTotal(customer.getTotal() + second.getTotal());
customer.setBalance(customer.getBalance() + second.getBalance());
return customer;
}
and everthing else stays as is.
回答2:
Use the following code:
Map<String, Customer> retObj =
listCust.stream()
.collect(Collectors.groupingBy(Customer::getName,
Collector.of(
Customer::new,
(c1, c2) -> {
c1.setName(c2.getName());
c1.setTotal(c1.getTotal() + c2.getTotal());
c1.setBalance(c1.getBalance() + c2.getBalance());
},
(c3, c4) -> {
c3.setTotal(c3.getTotal() + c4.getTotal());
c3.setBalance(c3.getBalance() + c4.getBalance());
return c3;
})));
System.out.println(retObj);
System.out.println(retObj.values()); //If you want only the list of all Customers
Output:
{
A=Customer [name=A, total=4500.0, balance=6500.0],
B=Customer [name=B, total=3000.0, balance=3500.0],
C=Customer [name=C, total=4000.0, balance=4500.0]
}
回答3:
The other answers here are great, but they mutate the Customer
instances in the input, which may be unexpected.
To avoid this, use a custom Collector
.
First, create a method that returns a Collector
that takes a Stream<Customer>
and merges them into a single Customer
:
public static Collector<Customer, Customer, Customer> customerCollector() {
return Collector.of(Customer::new, TestBench::merge,
(l, r) -> {
merge(l, r);
return l;
});
}
public static void merge(final Customer first, final Customer second) {
first.setName(second.getName());
first.setTotal(first.getTotal() + second.getTotal());
first.setBalance(first.getBalance() + second.getBalance());
}
This assumes that Customer
has a noargs constructor.
Then you can do:
Collection<Customer> result = listCust.stream()
.collect(groupingBy(Customer::getName, customerCollector()))
.values();
Note I use groupingBy
rather than toMap
- the groupingBy
collector is specifially designed to group elements.
回答4:
This can be done like so,
List<Customer> result = customers.stream().collect(Collectors.collectingAndThen(
Collectors.toMap(Customer::getName, c -> Arrays.asList(c.getTotal(), c.getBalance()),
(l1, l2) -> IntStream.range(0, l1.size()).mapToDouble(i -> l1.get(i) + l2.get(i)).boxed()
.collect(Collectors.toList())),
m -> m.entrySet().stream().map(e -> new Customer(e.getKey(), e.getValue().get(0), e.getValue().get(1)))
.collect(Collectors.toList())));
First create the Map
and then use a Finisher function to compute a List
of Customer
instances using the Map.Entry
.
来源:https://stackoverflow.com/questions/51452737/java-groupingby-sum-multiple-fields