Generika sind praktisch, nicht wahr? Aber ist Ihr Kopf nicht mit vielen Symbolen (Typargumenten) durcheinander? Die empfohlenen Schritte zum Generieren der API sind:
Nehmen wir ein konkretes Beispiel.
Ich habe den folgenden Code.
// In: List<SalesLine>
// Out:Karte mit dem Produktcode als Schlüssel und der Gesamtzahl der Verkäufe als Wert
Map<String, Integer> map =
list.stream().collect(
Collectors.groupingBy(SalesLine::getProductCode,
Collectors.summingInt(SalesLine::getQuantity)
));
Nehmen wir nun an, wir verwenden häufig den Prozess des "Gruppierens von Listenelementen mit einem bestimmten Schlüssel, Summieren einiger numerischer Werte für jedes Element und Zurückgeben in einer Karte". Machen wir es also zu einer Dienstprogrammmethode. Anstatt auf einmal in Generics zu schreiben, schreiben Sie zuerst solide.
public static Map<String, Integer> groupTotal(List<SalesLine> list) {
return list.stream().collect(
Collectors.groupingBy(SalesLine::getProductCode,
Collectors.summingInt(SalesLine::getQuantity)
));
}
Identifizieren Sie als Nächstes in diesem Prozess den Typ, den Sie variieren möchten. Das erste, was klar ist, ist die Art des Listenelements namens "SalesLine". Ersetzen Sie dies durch "T". (Zu diesem Zeitpunkt tritt ein Kompilierungsfehler auf.)
public static <T> Map<String, Integer> groupTotal(List<T> list) {
return list.stream().collect(
Collectors.groupingBy(T::getProductCode,
Collectors.summingInt(T::getQuantity)
));
}
Als nächstes möchten wir in der Lage sein, "Integer" - und proprietäre Typen zu verwenden, ohne den Schlüssel auf einen String zu beschränken. Ersetzen Sie also "String" durch "S". Der Kartenwert wird als Gesamtwert berechnet, belassen Sie ihn also als "Ganzzahl".
public static <S, T> Map<S, Integer> groupTotal(List<T> list) {
return list.stream().collect(
Collectors.groupingBy(T::getProductCode,
Collectors.summingInt(T::getQuantity)
));
}
Lassen Sie uns die T :: getProductCode
und T :: getQuantity
loswerden, die den Kompilierungsfehler verursachen.
Von diesen wird erwartet, dass sie Werte aus Objekten des Listenelementtyps (T
) extrahieren. Übergeben Sie sie daher als Argumente in der Funktionsschnittstelle.
In Anbetracht dessen, dass die Umwandlung in "T-> S" bzw. die Umwandlung in "T-> Ganzzahl" erfolgt, ist dies wie folgt.
public static <S, T> Map<S, Integer> groupTotal(
List<T> list, Function<T, S> keyExtractor, Function<T, Integer> valueExtractor) {
return list.stream().collect(
Collectors.groupingBy(keyExtractor::apply,
Collectors.summingInt(valueExtractor::apply)
));
}
Das ist es. Das Programm auf der Benutzerseite ist wie folgt.
Map<String, Integer> map = groupTotal(list, SalesLine::getProductCode, SalesLine::getQuantity);
Betrachten Sie das folgende Berichtsausgabeprogramm. Dies ist der sogenannte "Schlüsselunterbrechungs" -Prozess, der in Geschäftsanwendungen üblich ist, bei dem eine sortierte Liste von Verkaufsabrechnungen eingegeben und eine Zwischensummenzeile ausgegeben wird, wenn sich der Produktcode ändert.
ReportComponent.java
public String outputReport(List<SalesLine> sales) {
StringBuilder sb = new StringBuilder();
String currentProductCode = null;
int subtotalQty = 0;
int subtotalAmount = 0;
for (SalesLine sl: sales) {
String productCode = sl.getProductCode();
if (!productCode.equals(currentProductCode)) {
//Zwischensummenzeile bei Tastenumbruch ausgeben
if (currentProductCode != null) {
sb.append(makeSubtotalLine(currentProductCode, subtotalQty, subtotalAmount)).append("\n");
}
currentProductCode = productCode;
subtotalQty = 0;
subtotalAmount = 0;
}
sb.append(makeNormalLine(sl)).append("\n");
subtotalQty += sl.getQuantity();
subtotalAmount += sl.getAmount();
}
//Verarbeiten Sie die letzte Zwischensummengruppe
sb.append(makeSubtotalLine(currentProductCode, subtotalQty, subtotalAmount)).append("\n");
return sb.toString();
}
Das Ausgabebeispiel dieses Programms lautet wie folgt.
Details 2020-04-01 A 2 Stück 200 Yen
Details 2020-04-01 A 3 Stück 300 Yen
Details 2020-04-02 A 1 Stück 100 Yen
Details 2020-04-02 A 1 Stück 100 Yen
Zwischensumme A 7 Stück 700 Yen
Details 2020-04-01 B 1 Stück 150 Yen
Details 2020-04-02 B 2 Stück 300 Yen
Details 2020-04-02 B 2 Stück 300 Yen
Zwischensumme B 5 Stück 750 Yen
Details 2020-04-01 C 2 Stück 400 Yen
Zwischensumme C 2 Stück 400 Yen
Angenommen, Sie möchten den Prozess verallgemeinern, da die Verarbeitung von Schlüsselunterbrechungen in Geschäftsanwendungen häufig auftritt. Schreiben Sie zunächst eine solide Klasse für die Schlüsselunterbrechungsverarbeitung. Es sollte nur der Verarbeitungsablauf gemeinsam genutzt und die spezifische Ausgabelogik als Argument übergeben werden.
KeyBreakProcessor.java
public class KeyBreakProcessor {
private List<SalesLine> lines;
public KeyBreakProcessor(List<SalesLine> lines) {
this.lines = lines;
}
public void execute(Function<SalesLine, String> keyGenerator, Consumer<SalesLine> lineProcessor,
BiConsumer<String, List<SalesLine>> keyBreakLister) {
String currentKey = null;
List<SalesLine> subList = new ArrayList<>();
for (SalesLine line : lines) {
String key = keyGenerator.apply(line);
if (!key.equals(currentKey)) {
if (currentKey != null) {
keyBreakLister.accept(currentKey, subList);
subList = new ArrayList<>();
}
currentKey = key;
}
lineProcessor.accept(line);
subList.add(line);
}
keyBreakLister.accept(currentKey, subList);
}
}
Betrachten Sie den Typ, den Sie in diesem Prozess variieren möchten. Der Typ "SalesLine", der die Detailzeile darstellt, und der Typ "String" des Unterbrechungsschlüssels sind die Ziele. Wenn Sie sie also durch "L" bzw. "K" ersetzen, lautet das Ergebnis wie folgt.
GeneralKeyBreakProcessor.java
public class GeneralKeyBreakProcessor<L, K> {
private List<L> lines;
public GeneralKeyBreakProcessor(List<L> lines) {
this.lines = lines;
}
public void execute(Function<L, K> keyGenerator, Consumer<L> lineProcessor,
BiConsumer<K, List<L>> keyBreakLister) {
K currentKey = null;
List<L> subList = new ArrayList<>();
for (L line: lines) {
K key = keyGenerator.apply(line);
if (!key.equals(currentKey)) {
if (currentKey != null) {
keyBreakLister.accept(currentKey, subList);
subList = new ArrayList<>();
}
currentKey = key;
}
lineProcessor.accept(line);
subList.add(line);
}
keyBreakLister.accept(currentKey, subList);
}
}
Damit ist die generische API abgeschlossen. Die Seite des Berichtsausgabeprogramms ist wie folgt.
ReportComponent.java
public String outputReportWithGenerics(List<SalesLine> sales) {
GeneralKeyBreakProcessor<SalesLine, String> gkbp = new GeneralKeyBreakProcessor<>(sales);
final StringBuilder sb = new StringBuilder();
//Schlüsselgenerierung
Function<SalesLine, String> keyGenerator = SalesLine::getProductCode;
//1 Detailzeile verarbeiten und ausgeben
Consumer<SalesLine> processLine = sl -> sb.append(makeNormalLine(sl)).append("\n");
//Prozessposten gruppiert nach Schlüssel und Zwischensumme der Ausgabe
BiConsumer<String, List<SalesLine>> subTotal = (code, lines) -> {
int qty = lines.stream().mapToInt(SalesLine::getQuantity).sum();
int amount = lines.stream().mapToInt(SalesLine::getAmount).sum();
sb.append(makeSubtotalLine(code, qty, amount)).append("\n");
};
gkbp.execute(keyGenerator, processLine, subTotal);
return sb.toString();
}
Wenn Sie die Schritte befolgen, ist die Implementierung einer API mit Generics nicht beängstigend!
Dies ist eine generische Version des Berichtsausgabeprogramms, die jedoch weiterhin schwer zu lesen ist. Durch Überprüfen der API mithilfe der "Fluent API" -Technik wird die Lesbarkeit etwas verbessert.
ReportComponent.java
public String outputReportWithFluent(List<SalesLine> sales) {
FluentKeyBreakProcessor<SalesLine, String, String, String> processor = new FluentKeyBreakProcessor<>();
List<String> groupList =
processor.source(sales)
.key(SalesLine::getProductCode)
.eachLine(sl -> makeNormalLine(sl))
.whenKeyChanged((key, list1, list2) -> {
String lines = list2.stream().collect(Collectors.joining("\n")) + "\n";
int qty = list1.stream().mapToInt(SalesLine::getQuantity).sum();
int amount = list1.stream().mapToInt(SalesLine::getAmount).sum();
return lines + makeSubtotalLine(key, qty, amount) + "\n";
})
.execute();
return groupList.stream().collect(Collectors.joining());
}
Die Erklärung dieser Implementierung finden Sie ausführlich in Blog.