Java Liste streamen

MTwain

Mitglied
Hallo zusammen,

ich experimentiere grade mit .stream() und blicke nicht wirklich durch. Ich habe eine Rechnungsliste in der sich Objekte von Typ Rechnung befinden. Jedem Rechnungsobjekt hängt wiederum eine Liste von Produkt-Objekten an. Nun würde ich gerne die Rechnungsliste als CSV Datei speichern.
Erhofft hatte ich mir eine Ausgabe in der Form: Datum; Rechnungsnummer; Typ; [(Produktname; Anzahl; Preis) für jedes Produkt in der angehenagenen Produktliste des Rechnungobjektes]

Bisher habe ich:
Java:
public void saveList(ArrayList<Invoice> invoiceList) {

//      List<String> csvList= invoiceList.stream().map(inv -> String.format("%s; %s; %s; %s", inv.getDate(), inv.getInvNumb(), inv.getType(),                     inv.getSubProductList())).collect(Collectors.toList()); // bis dahin geht's noch, aber der Zugriff auf die angehangene Liste geht dann nicht mehr:

      List<String> csvList = invoiceList.stream().map(inv -> String.format("%s; %s; %s; %s;", inv.getDate().inv.getInvNumb().inv.getType(), inv.getSubProductList().stream().map(product -> String.format("%s; %d", product.getName().product.getCount())))).collect(Collectors.toList());
      for (String csv : csvList) {
          System.out.println(csv);
      }
  }
Als Output bekomme ich:
2021-01-07; OM20210107; Test; java.util.stream.ReferencePipeline$3@64cee07;

Ein rechnungsobjekt sieht so aus:
Java:
public class Invoice {

    private String invNumb;
    private LocalDate date;
    private String type;
    public ArrayList<Item> subProductList = new ArrayList<>();
}
Hat jemand eine Idee wie ich zum Ziel komme? Ich bin für jede Hilfe dankbar! 🙂
 
Zuletzt bearbeitet:
Da wäre mein Tipp: mach es doch einfach erst einmal ohne Streams!

Oder zerlegen es zumindest in vernünftige Teile. Du kannst ja den verschachtelten Stream in eine Methode auslagern.

Zu dem Problem: Du bekommst am Ende einen Stream von String. Das kannst Du natürlich nicht wie einen String ausgeben. Da fehlt also die 'Endverarbeitung' die dann auch die Verarbeitung startet und ein Ergebnis liefert.

Aber noch einmal zurück zum ersten Gedanken: so ein Code schreit nur danach, den Verursacher zu schlagen 🙂
 
Da wäre mein Tipp: mach es doch einfach erst einmal ohne Streams!

Oder zerlegen es zumindest in vernünftige Teile. Du kannst ja den verschachtelten Stream in eine Methode auslagern.

Zu dem Problem: Du bekommst am Ende einen Stream von String. Das kannst Du natürlich nicht wie einen String ausgeben. Da fehlt also die 'Endverarbeitung' die dann auch die Verarbeitung startet und ein Ergebnis liefert.

Aber noch einmal zurück zum ersten Gedanken: so ein Code schreit nur danach, den Verursacher zu schlagen 🙂
Okay also ertmal vielen Dank für die Antwort!

Ich fange mal von hinten an und versichere dir, der Code schlägt den Verursacher bereits ordentlich. Vorallem diese dem Rechnungsobejkt angehangene Liste und ich haben regelmäßig unsere Differenzen.

Der Stream von String wird doch durch String.format() zu einem String zusammengefasst, so dass nur ein String ausgegeben wird. Oder sind es doch zwei Strings? Die Ausgabe sieht aber nach einem aus? Liegt hier der Fehler?
Einmal String.format() für die Rechnugsdaten, einmal für die angehange Liste und dann noch einmal zum zusammenfügen von beidem?

Ok zerlegen:
Java:
List<String> csvList = invoiceList.stream()
.map(inv -> String.format("%s; %s; %s; %s;", inv.getDate(), inv.getInvNumb(), inv.getType(), 
inv.getSubProductList().stream()
.map(product -> String.format("%s; %d", product.getName(), product.getCount()))))
.collect(Collectors.toList());
Nanu, wer hatte denn die Kommas mit Punkten vertauscht? ^^ Trifft das deine Vorstellung von zerlegen? Was du mit auslagern meinst verstehe ich grade nicht.

Ohne Stream würde ich es mit zwei for-Schleifen versuchen.
Java:
for (int i=0; i< invoiceList.size(); i++) {
    String string1 = String.format("%s; %s; %s; %s;", inv.get(i).getDate(), inv.get(i).getInvNumb(), inv.get(i).getType());
    for (int j=0; j < SubProductList.size(); j++){
            String teilString2 = String.format("%s; %d;", inv.getsubProductList.get(j).getName(), inv.getsubProductList.get(j).getCount());
            String string2 = string2 + teilString2;
    }
    String string3 = string1 + string2; // kann man Strings eigentlich so verketten? 
}
Ich hoffe das haut so hin . Aber das wäre die Idee ohne Stream.
 
Also zergliedern wir es etwas...

Du hast einen äußeren Stream:
Java:
List<String> csvList = invoiceList.stream()
    .map(inv -> String.format(...)
    .collect(Collectors.toList());

Der ist soweit korrekt. Du erzeugst den Stream (stream()), machst etwas damit (.map, .filter, ...) und am Ende stößt Du die Verarbeitung an (.collect, .findXxxx, ...)

Dann hast Du einen inneren Stream. Und das kanz klar in Uppercase und bold: DAS MACHT MAN NUR, WENN MAN ERSCHLAGEN WERDEN WILL!

Also schauen wir und das einmal an, was Du da im String.format eigentlich machen wolltest:
.map(inv -> String.format("%s; %s; %s; %s;", inv.getDate(), inv.getInvNumb(), inv.getType(), getStringFromInv(inv)))

Ich habe da den inneren Stream einfach mal in eine Methode gepackt. Das unterteilt die Problematik etwas und dann hilft dir der Compiler auch etwas. Denn nun musst Du eine Methode füllen:
private static String getStringFromInv(Inventory inv) { ... }

Und da kann man dann den "inneren" Stream reinpacken:
Java:
return inv.getSubProductList().stream()
    .map(product -> String.format("%s; %d", product.getName(), product.getCount())))

Und hier verhaut Dich der Compiler. Denn Du hast da ja erst einen Stream<String> und keinen String. Und der Stream ist auch noch nicht "fertig". Damit ein Stream verarbeitet wird, muss am Ende etwas abschließendes kommen. Halt das collect, findAny, forEeach, ...

Da Du aber ein Ergebnis haben willst, muss das dann etwas sein, das taugt. Das könnte also z.B. ein .collect( Collectors.joining( "," ) ); sein oder so.

Also zwei wichtige Dinge, die Du hier verstehen solltest:
- Ganz wichtig ist der Punkt, der nicht in fett und Grossbuchstaben geschrieben wurde: So ein Stream solltest Du sozusagen abschließen. So ein abschließender Aufruf ist existenziell.
- Und dann bitte immer daran denken: Divide and conquer! Also teile Deinen Code bitte auf. Komplexer Code ist niemals gut. Also Komplexität immer sehr niedrig halten. Und bei streams ist das immer eine einfache kleine Methodein der nur ein Stream ist. Und da auch nichts zu komplexes.
--> Jedes .map .filter .irgendwas kommt auf eine eigene Zeile!
--> keine komplexen Dinge. Das String.format würde ich auch in eine Methode auslagern. (.map( inv -> getDescriptionFromInv(inv)) oder so)
Lieber mehr Methoden mit klaren Aussagen und schon kannst Du Code lesen wie Prosa! Die Komplexität ist plötzlich einfach und in jedem Teil überschaubar. Und Du hast viele kleinen Bausteine, die einfach zu testen sind!

Edit: Und ich hoffe, du verstehst mich da richtig. Das klingt so, dass wir Dir böse sind, wenn du schlechten Code schreibst oder so. Das ist Quatsch. Aber es hilft Dir, wenn Du Code schreibst, den Du selbst gut beherrschen kannst. Das vielleicht nur als kleiner dezenter Hinweis. Die deutliche Sprache wähle ich nur, weil ich Morgens immer Clowns frühstücke und weil das aus meiner Sicht der wichtigste Punkt ist, der Dich weiter bringt in Deinen Fähigkeiten. Die Streams kapiert man irgendwann irgendwie, aber dieses Clean Code ist eine Sache, die man nicht so kapieren kann sondern man muss es nur einfach nach und nach umsetzen und sich angewöhnen ...

Edit 2: Noch bei den komplexen Dingen am Ende ein Beispiel eingebaut, was ich meine.
 
Zuletzt bearbeitet von einem Moderator:
Also zergliedern wir es etwas...

Du hast einen äußeren Stream:
Java:
List<String> csvList = invoiceList.stream()
    .map(inv -> String.format(...)
    .collect(Collectors.toList());

Der ist soweit korrekt. Du erzeugst den Stream (stream()), machst etwas damit (.map, .filter, ...) und am Ende stößt Du die Verarbeitung an (.collect, .findXxxx, ...)

Dann hast Du einen inneren Stream. Und das kanz klar in Uppercase und bold: DAS MACHT MAN NUR, WENN MAN ERSCHLAGEN WERDEN WILL!

Also schauen wir und das einmal an, was Du da im String.format eigentlich machen wolltest:
.map(inv -> String.format("%s; %s; %s; %s;", inv.getDate(), inv.getInvNumb(), inv.getType(), getStringFromInv(inv)))

Ich habe da den inneren Stream einfach mal in eine Methode gepackt. Das unterteilt die Problematik etwas und dann hilft dir der Compiler auch etwas. Denn nun musst Du eine Methode füllen:
private static String getStringFromInv(Inventory inv) { ... }

Und da kann man dann den "inneren" Stream reinpacken:
Java:
return inv.getSubProductList().stream()
    .map(product -> String.format("%s; %d", product.getName(), product.getCount())))

Und hier verhaut Dich der Compiler. Denn Du hast da ja erst einen Stream<String> und keinen String. Und der Stream ist auch noch nicht "fertig". Damit ein Stream verarbeitet wird, muss am Ende etwas abschließendes kommen. Halt das collect, findAny, forEeach, ...

Da Du aber ein Ergebnis haben willst, muss das dann etwas sein, das taugt. Das könnte also z.B. ein .collect( Collectors.joining( "," ) ); sein oder so.

Also zwei wichtige Dinge, die Du hier verstehen solltest:
- Ganz wichtig ist der Punkt, der nicht in fett und Grossbuchstaben geschrieben wurde: So ein Stream solltest Du sozusagen abschließen. So ein abschließender Aufruf ist existenziell.
- Und dann bitte immer daran denken: Divide and conquer! Also teile Deinen Code bitte auf. Komplexer Code ist niemals gut. Also Komplexität immer sehr niedrig halten. Und bei streams ist das immer eine einfache kleine Methodein der nur ein Stream ist. Und da auch nichts zu komplexes.
--> Jedes .map .filter .irgendwas kommt auf eine eigene Zeile!
--> keine komplexen Dinge. Das String.format würde ich auch in eine Methode auslagern. (.map( inv -> getDescriptionFromInv(inv)) oder so)
Lieber mehr Methoden mit klaren Aussagen und schon kannst Du Code lesen wie Prosa! Die Komplexität ist plötzlich einfach und in jedem Teil überschaubar. Und Du hast viele kleinen Bausteine, die einfach zu testen sind!

Edit: Und ich hoffe, du verstehst mich da richtig. Das klingt so, dass wir Dir böse sind, wenn du schlechten Code schreibst oder so. Das ist Quatsch. Aber es hilft Dir, wenn Du Code schreibst, den Du selbst gut beherrschen kannst. Das vielleicht nur als kleiner dezenter Hinweis. Die deutliche Sprache wähle ich nur, weil ich Morgens immer Clowns frühstücke und weil das aus meiner Sicht der wichtigste Punkt ist, der Dich weiter bringt in Deinen Fähigkeiten. Die Streams kapiert man irgendwann irgendwie, aber dieses Clean Code ist eine Sache, die man nicht so kapieren kann sondern man muss es nur einfach nach und nach umsetzen und sich angewöhnen ...

Edit 2: Noch bei den komplexen Dingen am Ende ein Beispiel eingebaut, was ich meine.
Okay keine Streams im Stream streamen und dann nicht abschließen. Hab verstanden 🙂
Jetzt verstehe ich auch was du mit auslageren gemeint hast. Das ist natürlich echt eine gute Lösung. Ich werde das morgen gleich mal ausprobieren.

Vielen Dank für deine Tipps und Erläuterungen! Ich werde versuchen vorallem das mit dem Clean Code mehr zu beherzigen, da ist schon was dran.
Ich sehe übrigens keinen Anlass dafür deine Ernährung umzustellen. Ich finde deine Antwort sehr sachlich, pointiert und strukturiert. In dem Sinne: no offence taken. Nochmal vielen Dank für die ausführliche Antwort!
 
Also zergliedern wir es etwas...

Du hast einen äußeren Stream:
Java:
List<String> csvList = invoiceList.stream()
    .map(inv -> String.format(...)
    .collect(Collectors.toList());

Der ist soweit korrekt. Du erzeugst den Stream (stream()), machst etwas damit (.map, .filter, ...) und am Ende stößt Du die Verarbeitung an (.collect, .findXxxx, ...)

Dann hast Du einen inneren Stream. Und das kanz klar in Uppercase und bold: DAS MACHT MAN NUR, WENN MAN ERSCHLAGEN WERDEN WILL!

Also schauen wir und das einmal an, was Du da im String.format eigentlich machen wolltest:
.map(inv -> String.format("%s; %s; %s; %s;", inv.getDate(), inv.getInvNumb(), inv.getType(), getStringFromInv(inv)))

Ich habe da den inneren Stream einfach mal in eine Methode gepackt. Das unterteilt die Problematik etwas und dann hilft dir der Compiler auch etwas. Denn nun musst Du eine Methode füllen:
private static String getStringFromInv(Inventory inv) { ... }

Und da kann man dann den "inneren" Stream reinpacken:
Java:
return inv.getSubProductList().stream()
    .map(product -> String.format("%s; %d", product.getName(), product.getCount())))

Und hier verhaut Dich der Compiler. Denn Du hast da ja erst einen Stream<String> und keinen String. Und der Stream ist auch noch nicht "fertig". Damit ein Stream verarbeitet wird, muss am Ende etwas abschließendes kommen. Halt das collect, findAny, forEeach, ...

Da Du aber ein Ergebnis haben willst, muss das dann etwas sein, das taugt. Das könnte also z.B. ein .collect( Collectors.joining( "," ) ); sein oder so.

Also zwei wichtige Dinge, die Du hier verstehen solltest:
- Ganz wichtig ist der Punkt, der nicht in fett und Grossbuchstaben geschrieben wurde: So ein Stream solltest Du sozusagen abschließen. So ein abschließender Aufruf ist existenziell.
- Und dann bitte immer daran denken: Divide and conquer! Also teile Deinen Code bitte auf. Komplexer Code ist niemals gut. Also Komplexität immer sehr niedrig halten. Und bei streams ist das immer eine einfache kleine Methodein der nur ein Stream ist. Und da auch nichts zu komplexes.
--> Jedes .map .filter .irgendwas kommt auf eine eigene Zeile!
--> keine komplexen Dinge. Das String.format würde ich auch in eine Methode auslagern. (.map( inv -> getDescriptionFromInv(inv)) oder so)
Lieber mehr Methoden mit klaren Aussagen und schon kannst Du Code lesen wie Prosa! Die Komplexität ist plötzlich einfach und in jedem Teil überschaubar. Und Du hast viele kleinen Bausteine, die einfach zu testen sind!

Edit: Und ich hoffe, du verstehst mich da richtig. Das klingt so, dass wir Dir böse sind, wenn du schlechten Code schreibst oder so. Das ist Quatsch. Aber es hilft Dir, wenn Du Code schreibst, den Du selbst gut beherrschen kannst. Das vielleicht nur als kleiner dezenter Hinweis. Die deutliche Sprache wähle ich nur, weil ich Morgens immer Clowns frühstücke und weil das aus meiner Sicht der wichtigste Punkt ist, der Dich weiter bringt in Deinen Fähigkeiten. Die Streams kapiert man irgendwann irgendwie, aber dieses Clean Code ist eine Sache, die man nicht so kapieren kann sondern man muss es nur einfach nach und nach umsetzen und sich angewöhnen ...

Edit 2: Noch bei den komplexen Dingen am Ende ein Beispiel eingebaut, was ich meine.
Okay keine Streams im Stream streamen und dann nicht abschließen. Hab verstanden 🙂
Jetzt verstehe ich auch was du mit auslageren gemeint hast. Das ist natürlich echt eine gute Lösung. Ich werde das morgen gleich mal ausprobieren.

Vielen Dank für deine Tipps und Erläuterungen! Ich werde versuchen vorallem das mit dem Clean Code mehr zu beherzigen, da ist schon was dran.
Ich sehe übrigens keinen Anlass dafür deine Ernährung umzustellen. Ich finde deine Antwort sehr sachlich, pointiert und strukturiert. In dem Sinne: no offence taken. Nochmal vielen Dank für die ausführliche Antwort!
 
Ich habs eben ausprobiert und des funktioniert absolut so wie ich es mir das gewünscht hatte.
Nochmal der Vollständigkeit halber der Code:
Java:
private static void saveList(ArrayList<Invoice> invoiceList) {

      List<String> csvList = invoiceList.stream()
              .map(inv -> String.format("%s;%s;%s;%s;", inv.getDate(), inv.getInvNumb(), inv.getType(), getStringFromInv(inv)))
              .collect(Collectors.toList());
      for (String csv : csvList) {
          System.out.println(csv);
      }
  }
  
  private static String getStringFromInv(Invoice inv) {
      
      return inv.getSubProductList().stream()
                .map(product -> String.format("%s;%d;%f", product.getName(), product.getCount(), product.getPrice()))
                .collect(Collectors.joining(";"));
  }
Ich konnte mich noch nicht dazu durchringen den String.format Teil auszugliedern, aber man beachte die Zeilenumbrüche!
Nochmal Danke für die schnelle und kompetente Hilfe!
 
Die Bezeichner sind aber noch nicht ideal. getStringFromInv hatte ich nur als Beispiel genannt, da ich mich nicht näher mit dem Inhalt beschäftigt habe. Da wäre dann ein getProductListString oder so besser. Dass es vom Invoice kommt, ist durch den Parameter ja gegeben. "String" im Namen sieht dubios aus, aber bei getProductList hätte ich halt eine List<Product> erwartet...

Das sind aber wieder nur halbgare Dinge, die ich Dir vorsetze - aber da sollte man ggf. mal kurz nachdenken um vernünftige Bezeichner zu haben.
 
Die Bezeichner sind aber noch nicht ideal. getStringFromInv hatte ich nur als Beispiel genannt, da ich mich nicht näher mit dem Inhalt beschäftigt habe. Da wäre dann ein getProductListString oder so besser. Dass es vom Invoice kommt, ist durch den Parameter ja gegeben. "String" im Namen sieht dubios aus, aber bei getProductList hätte ich halt eine List<Product> erwartet...

Das sind aber wieder nur halbgare Dinge, die ich Dir vorsetze - aber da sollte man ggf. mal kurz nachdenken um vernünftige Bezeichner zu haben.
Naja den Bezeichner habe ich natürlich dem Schöpfer der Methode gewidmet. Wie wäre es mit getSubstream? getProductSubstream? Deine Wahl. Aber es bleibt für immer und ewig im Code ;-)
 

Neue Themen


Zurück
Oben