Ich habe oft das gleiche Problem. Ich muss die Läufe eines Lambda für die Verwendung außerhalb des Lambda zählen. Z.B:
myStream.stream().filter(...).forEach(item->{ ... ; runCount++);
System.out.println("The lambda ran "+runCount+"times");
Das Problem ist, dass runCount endgültig sein muss, daher kann es kein int sein. Es kann keine Ganzzahl sein, da dies unveränderlich ist. Ich könnte es auf Klassenebene variabel machen (dh ein Feld), aber ich werde es nur in diesem Codeblock brauchen. Ich weiß, dass es verschiedene Möglichkeiten gibt. Ich bin nur neugierig, was Ihre bevorzugte Lösung dafür ist. Verwenden Sie eine AtomicInteger- oder Array-Referenz oder eine andere Methode?
java
lambda
java-8
java-stream
Stuart Marks
quelle
quelle
AtomicInteger
hier verwenden.Antworten:
Lassen Sie mich Ihr Beispiel zur Diskussion ein wenig neu formatieren:
long runCount = 0L; myStream.stream() .filter(...) .forEach(item -> { foo(); bar(); runCount++; // doesn't work }); System.out.println("The lambda ran " + runCount + " times");
Wenn Sie einen Zähler wirklich aus einem Lambda heraus inkrementieren müssen, besteht die typische Methode darin, den Zähler zu einem
AtomicInteger
oder zu machenAtomicLong
und dann eine der Inkrementierungsmethoden darauf aufzurufen.Sie könnten ein einzelnes Element
int
oderlong
Array verwenden, dies hätte jedoch Race-Bedingungen, wenn der Stream parallel ausgeführt wird.Beachten Sie jedoch, dass der Stream endet
forEach
, was bedeutet, dass es keinen Rückgabewert gibt. Sie können dasforEach
in a ändernpeek
, das die Elemente durchläuft, und sie dann zählen:long runCount = myStream.stream() .filter(...) .peek(item -> { foo(); bar(); }) .count(); System.out.println("The lambda ran " + runCount + " times");
Das ist etwas besser, aber immer noch etwas seltsam. Der Grund ist, dass
forEach
undpeek
können ihre Arbeit nur über Nebenwirkungen erledigen. Der aufkommende Funktionsstil von Java 8 besteht darin, Nebenwirkungen zu vermeiden. Wir haben ein wenig davon getan, indem wir das Inkrement des Zählers in einecount
Operation im Stream extrahiert haben . Andere typische Nebenwirkungen sind das Hinzufügen von Elementen zu Sammlungen. In der Regel können diese durch Verwendung von Kollektoren ersetzt werden. Aber ohne zu wissen, welche Arbeit Sie tatsächlich versuchen, kann ich nichts Spezifischeres vorschlagen.quelle
peek
funktioniert, sobaldcount
Implementierungen Verknüpfungen fürSIZED
Streams verwenden. Das ist vielleicht nie ein Problem mitfilter
ed stream, kann aber große Überraschungen bereiten, wenn jemand den Code zu einem späteren Zeitpunkt ändert…final AtomicInteger i = new AtomicInteger(1);
und irgendwo in Ihrem Lambda verwendeni.getAndAdd(1)
. Halte inne und erinnere dich, wie schönint i=1; ... i++
früher war.Incrementable
numerische Klassen implementierenAtomicInteger
und Operatoren als++
ausgefallene Funktionen deklarieren würde, müssten die Operatoren nicht überladen werden und verfügen dennoch über gut lesbaren Code.Als Alternative zur Synchronisierung von AtomicInteger könnte stattdessen ein ganzzahliges Array verwendet werden. Solange dem Verweis auf das Array kein anderes Array zugewiesen wird (und das ist der Punkt), kann er als endgültige Variable verwendet werden, während sich die Werte der Felder beliebig ändern können.
int[] iarr = {0}; // final not neccessary here if no other array is assigned stringList.forEach(item -> { iarr[0]++; // iarr = {1}; Error if iarr gets other array assigned });
quelle
foreach
direkte Sammlung (ohne Streams) ist dies ein ausreichend guter Ansatz. Vielen Dank !!AtomicInteger runCount = 0L; long runCount = myStream.stream() .filter(...) .peek(item -> { foo(); bar(); runCount.incrementAndGet(); }); System.out.println("The lambda ran " + runCount.incrementAndGet() + "times");
quelle
runCount
. Ich vermute, Sie wollten nur einen von ihnen haben, aber welchen?AtomicInteger
hat mir geholfen, aber ich würde es mitnew AtomicInteger(0)
Sie sollten AtomicInteger nicht verwenden, Sie sollten keine Dinge verwenden, es sei denn, Sie haben einen wirklich guten Grund dafür. Und der Grund für die Verwendung von AtomicInteger ist möglicherweise, dass nur gleichzeitige Zugriffe oder ähnliches zugelassen werden.
Wenn es um Ihr Problem geht;
Der Halter kann zum Halten und Inkrementieren in einem Lambda verwendet werden. Und nachdem Sie es erhalten haben, rufen Sie runCount.value auf
Holder<Integer> runCount = new Holder<>(0); myStream.stream() .filter(...) .forEach(item -> { foo(); bar(); runCount.value++; // now it's work fine! }); System.out.println("The lambda ran " + runCount + " times");
quelle
javax.xml.ws.Holder
.Für mich hat dies den Trick getan, hoffentlich findet es jemand nützlich:
AtomicInteger runCount= new AtomicInteger(0); myStream.stream().filter(...).forEach(item->{ ... ; runCount.getAndIncrement();); System.out.println("The lambda ran "+runCount.get()+"times");
In der Java-Dokumentation von getAndIncrement () heißt es:
quelle
Eine andere Möglichkeit, dies zu tun (nützlich, wenn Sie möchten, dass Ihre Anzahl nur in einigen Fällen erhöht wird, z. B. wenn eine Operation erfolgreich war), ist die Verwendung von
mapToInt()
undsum()
:int count = myStream.stream() .filter(...) .mapToInt(item -> { foo(); if (bar()){ return 1; } else { return 0; }) .sum(); System.out.println("The lambda ran " + count + "times");
Wie Stuart Marks bemerkte, ist dies immer noch etwas seltsam, da Nebenwirkungen nicht vollständig vermieden werden (je nachdem, was
foo()
und wasbar()
getan wird).Eine andere Möglichkeit, eine Variable in einem Lambda zu erhöhen, auf das außerhalb zugegriffen werden kann, ist die Verwendung einer Klassenvariablen:
public class MyClass { private int myCount; // Constructor, other methods here void myMethod(){ // does something to get myStream myCount = 0; myStream.stream() .filter(...) .forEach(item->{ foo(); myCount++; }); } }
In diesem Beispiel ist die Verwendung einer Klassenvariablen für einen Zähler in einer Methode wahrscheinlich nicht sinnvoll. Daher würde ich davor warnen, es sei denn, es gibt einen guten Grund dafür. Das Beibehalten von Klassenvariablen,
final
wenn möglich, kann im Hinblick auf die Thread-Sicherheit usw. hilfreich sein ( eine Diskussion zur Verwendung finden Sie unter http://www.javapractices.com/topic/TopicAction.do?Id=23final
).Https://www.infoq.com/articles/Java-8-Lambdas-A-Peek-Under-the-Hood bietet einen detaillierten Überblick darüber, warum Lambdas so funktionieren, wie sie funktionieren .
quelle
Wenn Sie kein Feld erstellen möchten, weil Sie es nur lokal benötigen, können Sie es in einer anonymen Klasse speichern:
int runCount = new Object() { int runCount = 0; { myStream.stream() .filter(...) .peek(x -> runCount++) .forEach(...); } }.runCount;
Seltsam, ich weiß. Die temporäre Variable bleibt jedoch außerhalb des lokalen Bereichs.
quelle
Reduzieren funktioniert auch, Sie können es so verwenden
quelle
Eine andere Alternative ist die Verwendung von Apache Commons MutableInt.
MutableInt cnt = new MutableInt(0); myStream.stream() .filter(...) .forEach(item -> { foo(); bar(); cnt.increment(); }); System.out.println("The lambda ran " + cnt.getValue() + " times");
quelle
AtomicInteger runCount = new AtomicInteger(0); elements.stream() //... .peek(runCount.incrementAndGet()) .collect(Collectors.toList()); // runCount.get() should have the num of times lambda code was executed
quelle