2017-03-23 3 views
8

Ich habe Definition POJO wie folgt:Java 8 Lambda Collectors.summing Lange mehrere Spalten?

class EmployeeDetails{ 
private String deptName; 
private Double salary; 
private Double bonus; 
... 
} 

Derzeit i Lambda-Ausdruck für die Gruppe haben von 'deptName' als:

$set.stream().collect(Collectors.groupingBy(EmployeeDetails::getDeptName, 
           Collectors.summingLong(EmployeeDetails::getSalary)); 

Frage Ist es möglich, Summe mehr als eine Spalte? Ich muss die Summe auf beide Felder salary and bonus in einem Ausdruck statt mehrmals berechnen?

SQL Darstellung wäre:

SELECT deptName,SUM(salary),SUM(bonus) 
FROM TABLE_EMP 
GROUP BY deptName; 

Antwort

6

Sie benötigen eine zusätzliche Klasse erstellen, die Ihre 2 zusammengefasst Zahlen (Gehalt und Bonus) halten wird. Und ein benutzerdefinierter Sammler.

Angenommen, Sie haben

private static final class Summary { 
    private double salarySum; 
    private double bonusSum; 

    public Summary() { 
     this.salarySum = 0; 
     this.bonusSum = 0; 
    } 

    @Override 
    public String toString() { 
     return "Summary{" + 
       "salarySum=" + salarySum + 
       ", bonusSum=" + bonusSum + 
       '}'; 
    } 
} 

für Summen zu halten. Dann brauchen Sie einen Kollektor wie folgt aus:

private static class EmployeeDetailsSummaryCollector implements Collector<EmployeeDetails, Summary, Summary> { 
    @Override 
    public Supplier<Summary> supplier() { 
     return Summary::new; 
    } 

    @Override 
    public BiConsumer<Summary, EmployeeDetails> accumulator() { 
     return (summary, employeeDetails) -> { 
      summary.salarySum += employeeDetails.salary; 
      summary.bonusSum += employeeDetails.bonus; 
     }; 
    } 

    @Override 
    public BinaryOperator<Summary> combiner() { 
     return (summary, summary1) -> { 
      summary.salarySum += summary1.salarySum; 
      summary.bonusSum += summary1.bonusSum; 
      return summary; 
     }; 
    } 

    @Override 
    public Function<Summary, Summary> finisher() { 
     return Function.identity(); 
    } 

    @Override 
    public Set<Characteristics> characteristics() { 
     return EnumSet.of(Collector.Characteristics.IDENTITY_FINISH); 
    } 
} 

Mit diesen Klassen können Sie Ihre Ergebnisse wie

final List<EmployeeDetails> employees = asList(
     new EmployeeDetails(/* deptName */"A", /* salary */ 100d, /* bonus */ 20d), 
     new EmployeeDetails("A", 150d, 10d), 
     new EmployeeDetails("B", 80d, 5d), 
     new EmployeeDetails("C", 100d, 20d) 
); 

final Collector<EmployeeDetails, Summary, Summary> collector = new EmployeeDetailsSummaryCollector(); 
final Map<String, Summary> map = employees.stream() 
     .collect(Collectors.groupingBy(o -> o.deptName, collector)); 
System.out.println("map = " + map); 

sammeln, die diese druckt:

map = {A=[salary=250.0, bonus=30.0], B=[salary=80.0, bonus=5.0], C=[salary=100.0, bonus=20.0]} 
3

Ich weiß, dass Sie Ihre Antwort haben , aber hier ist mein Take (ich schrieb, während der andere gepostet wurde). Es gibt bereits eine Pair in Java in Form von AbstractMap.SimpleEntry.

System.out.println(Stream.of(new EmployeeDetails("first", 50d, 7d), new EmployeeDetails("first", 50d, 7d), 
      new EmployeeDetails("second", 51d, 8d), new EmployeeDetails("second", 51d, 8d)) 
      .collect(Collectors.toMap(EmployeeDetails::getDeptName, 
        ed -> new AbstractMap.SimpleEntry<>(ed.getSalary(), ed.getBonus()), 
        (left, right) -> { 
         double key = left.getKey() + right.getKey(); 
         double value = left.getValue() + right.getValue(); 
         return new AbstractMap.SimpleEntry<>(key, value); 
        }, HashMap::new))); 
+0

Diese Lösung funktioniert auch gut! aber auf zwei Spalten beschränkt, oder? Wenn wir 3 oder 4 Spalten benötigen, erhöht sich die Komplexität. – gpa

1

Gruppierung nach ist eine Terminaloperation, die eine Karte ergibt. Die von der groupingBy im folgenden Code erstellte Karte ist eine Map<String, List<EmployeeDetails>>. Ich erstelle einen neuen Stream mit der Map-Methode entrySet. Ich erstelle dann eine neue Map mit Collectors.toMap. Bei diesem Ansatz wird die Verkettung von Methoden verwendet, um das Erstellen einer anderen Klasse und das Erstellen von präziseren Code zu vermeiden.

details.stream() 
    .collect(Collectors.groupingBy(EmployeeDetails::getDeptName)) 
    .entrySet() 
    .stream() 
    .collect(Collectors.toMap(x->x.getKey(), x->x.getValue() 
     .stream() 
     .mapToDouble(y -> y.getSalary() + y.getBonus()) 
     .sum()));