List.stream().foreach() vs List.foreach()

Hey.

Ich hab unbewussterweise in mehreren Stellen stream().foreach stehen und dann mal wieder nur foreach(). Ist mir nie aufgefallen, das es 2 verschieden Methoden sind. Hab dann mal gegoogled und im Prinzip nur das gefunden:

Aber ich hab den Unterschied nicht so ganz verstanden, könnte mir wer von euch erklären, wann ich was nehme? Im Code habe ich keinen Unterschied feststellen können. Das was ich aus obigen Thread mitnehmen konnte ist, das die Stream Variante ne undefinierte Reihenfolge hat, also es nicht sicher ist, in welcher Reihenfolge die Elemente durchgegangen werden, während bei der normalen foreach der Iterator der Liste benutzt wird und daher auch die eigentliche Reihenfolge, wie die Elemente in der Liste sind, sowie das bei jedem durchgang schon geprüft wird, obn Modifikationen an der Liste vorgenommen wurden. Was jetz aber der Vorteil davon is oder wan man was nimmt hab ich jetz nicht rausgelesen.

So wie ich den SO-Beitrag interpretiere kann stream().foreach() in mehreren Threads ausgeführt werden.
Daher bietet sich ein impliziter Geschwindigkeitsvorteil. Leider geht der Beitrag nicht darauf ein, ob man explizit stream().parallel().foreach() einfügen muss, um die parallele Abarbeitung zu aktivieren, der Verfasser scheint aber davon auszugehen, dass parallele Ausführung bei Streams implizit ist. Falls nicht scheint der offensichtliche Unterschied der zu sein, dass stream().foreach() erlaubt, Elemente hinzuzufügen oder zu entfernen, während Collection.foreach() eine CME werfern würde.

Wenn das stream().foreach() tatsächlich in Threads abläuft kann man nicht davon ausgehen, dass das übergebe Lambda Änderungen aus (logischen) früheren Iterationen sieht (weak consistency). Zum Beispiel könnte ein Sortier-Algorithmus nicht davon ausgehen, dass die Elemente vor dem aktuellen Eintrag tatsächlich schon sortiert sind, weil deren Sortierung in einem anderen Thread statt gefunden haben könnte bzw. dort noch im Gange ist.

In der Konsequenz bedeutet das, dass man stream()[.parallel()].foreach() nutzen solle, wenn das Lambda nicht mit anderen als dem aktuellen Element der Collection arbeitet und diese sehr groß (>1Mio Einträge) sein können bzw. man Elemente hinzufügen/entfernen möchte.
das bevorzugt Collection.foreach()

Man kann diese Bedingung natürlich auch anders herum formulieren und damit Collecton.stream()[.parallel()].foreach() zum Standard erheben, so lange das Lambda nicht auf andere Elemente der Collection zugreift.

Der Nachteil von letzterem Ansatz ist IMHO, dass Fehler, die sich dann auf Grund der weak consistency ergeben zum einen sehr spät in Erscheinung treten (idR. erst in der Produktion mit realen Datenmengen) und zum anderen sehr schwer zu finden sind. Wenn man stream().parallel() später aus Performanz-Gründen hinzu fügt ist die Wahrscheinlichkeit höher, dass man sich an die weak consistency erinnert und das Lambda auf die entsprechenden Auswirkungen prüft.

bye
TT

forEach ist einfach eine neue (Default-)Methode am Iterable-Interface, es verwendet intern einfach eine Schleife über den Iterator.

Auch Stream hat ein forEach - ein Stream kann ja auf viele Arten erzeugt werden, nicht nur aus einem Iterable heraus, und da ist es praktisch, diese Methode zu haben.

Wenn du schon ein Iterable hast und nicht auf parallele Ausführung angewiesen bist (was natürlich die Reihenfolge der Abarbeitung nicht mehr garantiert), kannst du getrost die Iterable-Version nehmen.

Okay Vielen Dank! Mit Streams hatte ich noch nicht viel zu tun außerhalb von Lambdas in Inneren Klassen, da ich nciht weiß wo ich sowas den üblichen Methoden vorziehen sollte, vorallem da ich nur schwer sagen kann, was da passiert:

                PersonUtil.getOtherPersonList()
        ).flatMap(pList -> pList.stream().map(p -> p.getVorname() + " " + p.getName()))
                .forEach(System.out::println);```

Für mich schaut das sehr kryptisch aus und den Unterschied zwischen flatMap und Map muss ich mri auch nochmal anschauen...

Dazu verstehe ich nciht warum das funktioniert:
```public class Kommandos {

    public interface Process {

        public void kill();

        public void suspend();

    }

    public interface Command {

        public void execute();

    }

    public static class ProcessManager {

        private List<Command> commands = new ArrayList<>();

        public void addCommand(Command cmd) {
            commands.add(cmd);
        }

        public void execute() {
            commands.forEach(Command::execute);
        }
    }

    public static void main(String[] args) {
        Process p = new Process() {
            @Override
            public void kill() {
                System.out.println("Töten");
            }

            @Override
            public void suspend() {
                System.out.println("Schlafen");
            }
        };
        Process p2 = new Process() {
            @Override
            public void kill() {
                System.out.println("Auch töten");
            }

            @Override
            public void suspend() {
                System.out.println("Auch schlafen");
            }
        };
        
        ProcessManager manager = new ProcessManager();
        manager.addCommand(p::suspend);
        manager.addCommand(p::kill);
        manager.addCommand(p2::suspend);
        manager.addCommand(p2::kill);
        manager.execute();
    }

}```

Wieso schmeißt das keinen Fehler? Die MEthoden von Process sind doch keine Commands. 

Bis ich und Streams und allgemein die ganzen Functions Freunde werden wirds noch dauern ^.^

[QUOTE=Darse]Okay Vielen Dank! Mit Streams hatte ich noch nicht viel zu tun außerhalb von Lambdas in Inneren Klassen, da ich nciht weiß wo ich sowas den üblichen Methoden vorziehen sollte, vorallem da ich nur schwer sagen kann, was da passiert:

                PersonUtil.getOtherPersonList()
        ).flatMap(pList -> pList.stream().map(p -> p.getVorname() + " " + p.getName()))
                .forEach(System.out::println);```

Für mich schaut das sehr kryptisch aus und den Unterschied zwischen flatMap und Map muss ich mri auch nochmal anschauen...
[/quote]

Auseinanderklamüsert: `Stream.of(a,b,c)` liefert einen Stream genau dieser Elemente a,b,c. Hier sind das allerdings zwei Listen. Der Lambda-Ausdruck `pList -> pList.stream().map(p -> p.getVorname() + " " + p.getName())` übersetzt eine Liste von Personen in einen Stream von Strings (bestehend aus den Namen der Personen). Da wir mit zwei Streams (aus den zwei Listen) gestartet sind, würde ein `map` hier einen `Stream<Stream<String>>` liefern, aber wir wollen keinen verschachtelten, sondern einen "flachen" Stream von Namen - und genau das macht `flatMap`: Es verkettet die entstehenden Streams zu einem einzigen. Im letzten Schritt werden einfach alle Stream-Elemente ausgegeben. Also alles nicht so schwer.

Wenn du dir nicht sicher bist, ob du `map` oder `flatMap` brauchst: Frage dich einfach, ob sich die Anzahl der Elemente durch die Operation ändern kann oder nicht. Wenn du z.B. deine eigene `filter`-Methode schreiben wolltest, ginge das nur mit `flatMap`: Wenn das Prädikat erfüllt ist, lieferst du einen Stream mit dem jeweiligen Element, und wenn nicht, dann einen leeren Stream.



> Dazu verstehe ich nciht warum das funktioniert:
> [spoiler]
> ```public class Kommandos {

>     public interface Process {

>         public void kill();

>         public void suspend();

>     }

>     public interface Command {

>         public void execute();

>     }

>     public static class ProcessManager {

>         private List<Command> commands = new ArrayList<>();

>         public void addCommand(Command cmd) {
>             commands.add(cmd);
>         }

>         public void execute() {
>             commands.forEach(Command::execute);
>         }
>     }

>     public static void main(String[] args) {
>         Process p = new Process() {
>             @Override
>             public void kill() {
>                 System.out.println("Töten");
>             }

>             @Override
>             public void suspend() {
>                 System.out.println("Schlafen");
>             }
>         };
>         Process p2 = new Process() {
>             @Override
>             public void kill() {
>                 System.out.println("Auch töten");
>             }

>             @Override
>             public void suspend() {
>                 System.out.println("Auch schlafen");
>             }
>         };
>         
>         ProcessManager manager = new ProcessManager();
>         manager.addCommand(p::suspend);
>         manager.addCommand(p::kill);
>         manager.addCommand(p2::suspend);
>         manager.addCommand(p2::kill);
>         manager.execute();
>     }

> }```
> [/spoiler]

> Wieso schmeißt das keinen Fehler? Die MEthoden von Process sind doch keine Commands.


Jede Methode kann über eine Methodenreferenz zu einem entsprechenden SAM-Typ (Interface mit genau einer abstrakten Methode) umgewandelt werden, solange die Signatur passt.

die Methoden sind vor allem überhaupt keine normalen Typen/ Objekte,

wäre man dazu ganz strikt, dann könnte man Methoden ja wenn überhaupt nur dorthin übergeben, wo Parameter vom Typ Method oder so vorliegen,
aufwendig noch eigene Methoden-Parameter dieser Parmeter-Methode zu definieren,
ziemlich eingeschränkt

so wie hier ist es etwas flexibler, statt Methoden auch normale Objekte zu übergeben, das Interface bietet etwas mehr Doku-Möglichkeiten,
aber natürlich auch unübersichtlich


zum Vergleich geht auch Swing-ActionListener:


        JButton b = null;
        b.addActionListener(p::killAction);

solange also eine Methode von ihrer Signatur her zu einem einmethodigen Interface passt kann die Methode übergeben werden

Okay, vielen Dank für die Erklärung