Często mam ten sam problem. Muszę policzyć przebiegi lambda do użycia poza lambdą .
Na przykład:
myStream.stream().filter(...).forEach(item -> { ... ; runCount++});
System.out.println("The lambda ran " + runCount + "times");
Problem polega na tym, że runCount musi być final
, więc nie może to być int
. Nie może to być, Integer
ponieważ jest niezmienne . Mógłbym uczynić to zmienną na poziomie klasy (tj. Pole), ale będę potrzebować jej tylko w tym bloku kodu.
Wiem, że są różne sposoby, jestem po prostu ciekawy, jakie jest Twoje preferowane rozwiązanie?
Czy używasz AtomicInteger
odwołania do tablicy lub w inny sposób?
java
lambda
java-8
java-stream
informatik01
źródło
źródło
AtomicInteger
tutaj.Odpowiedzi:
Pozwólcie, że przeformatuję nieco twój przykład na potrzeby dyskusji:
long runCount = 0L; myStream.stream() .filter(...) .forEach(item -> { foo(); bar(); runCount++; // doesn't work }); System.out.println("The lambda ran " + runCount + " times");
Jeśli naprawdę potrzebujesz zwiększyć licznik z poziomu lambdy, typowym sposobem na to jest ustawienie licznika na
AtomicInteger
or,AtomicLong
a następnie wywołanie jednej z metod inkrementacji.Możesz użyć pojedynczego elementu
int
lublong
tablicy, ale miałoby to warunki wyścigu, gdyby strumień był uruchamiany równolegle.Ale zauważ, że strumień kończy się na
forEach
, co oznacza, że nie ma wartości zwracanej. Możesz zmienić naforEach
apeek
, który przekazuje elementy, a następnie je policzyć:long runCount = myStream.stream() .filter(...) .peek(item -> { foo(); bar(); }) .count(); System.out.println("The lambda ran " + runCount + " times");
To jest trochę lepsze, ale wciąż trochę dziwne. Powodem jest to
forEach
ipeek
mogą wykonywać swoją pracę tylko poprzez skutki uboczne. Wyłaniający się styl funkcjonalny Java 8 polega na unikaniu skutków ubocznych. Zrobiliśmy trochę tego, wyodrębniając przyrost licznika docount
operacji na strumieniu. Inne typowe skutki uboczne to dodawanie elementów do kolekcji. Zwykle można je wymienić za pomocą kolektorów. Ale nie wiedząc, jaką rzeczywistą pracę próbujesz wykonać, nie mogę zasugerować nic bardziej szczegółowego.źródło
peek
przestaje działać, gdycount
implementacje zaczną używać skrótów dlaSIZED
strumieni. To może nigdy nie stanowić problemu zfilter
ed streamem, ale może stworzyć wielkie niespodzianki, jeśli ktoś zmieni kod w późniejszym czasie…final AtomicInteger i = new AtomicInteger(1);
i gdzieś w swojej lambdzie użyji.getAndAdd(1)
. Zatrzymaj się i pamiętaj, jak miło byłoint i=1; ... i++
kiedyśIncrementable
w klasach numerycznych, w tymAtomicInteger
i deklarowała operatory, takie jak++
funkcje wyglądające na fantazyjne, nie potrzebowalibyśmy przeciążania operatorów i nadal mielibyśmy bardzo czytelny kod.Jako alternatywę dla kłopotliwej synchronizacji AtomicInteger można zamiast tego użyć tablicy liczb całkowitych . Dopóki odwołanie do tablicy nie ma przypisanej innej tablicy (i o to chodzi), może być użyte jako zmienna końcowa , podczas gdy wartości pól mogą się dowolnie zmieniać .
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 });
źródło
foreach
bezpośredniego odbioru (bez strumieni) jest to wystarczająco dobre podejście. dzięki !!AtomicInteger runCount = 0L; long runCount = myStream.stream() .filter(...) .peek(item -> { foo(); bar(); runCount.incrementAndGet(); }); System.out.println("The lambda ran " + runCount.incrementAndGet() + "times");
źródło
runCount
. Podejrzewam, że chciałeś mieć tylko jeden z nich, ale który?AtomicInteger
Mi pomógł, ale chciałbym init, tonew AtomicInteger(0)
Nie powinieneś używać AtomicInteger, nie powinieneś używać rzeczy, chyba że masz naprawdę dobry powód, aby ich używać. Przyczyną korzystania z AtomicInteger może być tylko zezwolenie na równoczesny dostęp lub takie jak.
Jeśli chodzi o twój problem;
Uchwyt może służyć do przechowywania i zwiększania wartości wewnątrz lambda. A potem możesz to uzyskać, wywołując runCount.value
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");
źródło
javax.xml.ws.Holder
.Dla mnie to załatwiło sprawę, mam nadzieję, że ktoś uzna to za przydatne:
AtomicInteger runCount = new AtomicInteger(0); myStream.stream().filter(...).forEach(item -> runCount.getAndIncrement()); System.out.println("The lambda ran " + runCount.get() + "times");
getAndIncrement()
Dokumentacja Java stwierdza:źródło
Innym sposobem zrobienia tego (przydatnym, jeśli chcesz, aby liczba była zwiększana tylko w niektórych przypadkach, na przykład w przypadku pomyślnej operacji), jest coś takiego, jak użycie
mapToInt()
isum()
:int count = myStream.stream() .filter(...) .mapToInt(item -> { foo(); if (bar()){ return 1; } else { return 0; }) .sum(); System.out.println("The lambda ran " + count + "times");
Jak zauważył Stuart Marks, jest to nadal nieco dziwne, ponieważ nie pozwala całkowicie uniknąć skutków ubocznych (w zależności od tego, co
foo()
ibar()
robisz).Innym sposobem inkrementacji zmiennej w lambdzie, która jest dostępna poza nią, jest użycie zmiennej klasy:
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++; }); } }
W tym przykładzie użycie zmiennej klasy jako licznika w jednej metodzie prawdopodobnie nie ma sensu, więc ostrzegam przed tym, chyba że jest ku temu dobry powód. Utrzymywanie zmiennych klas,
final
jeśli to możliwe, może być pomocne pod względem bezpieczeństwa wątków itp. (Zobacz http://www.javapractices.com/topic/TopicAction.do?Id=23, aby zapoznać się z dyskusją na temat używaniafinal
).Aby lepiej zrozumieć, dlaczego lambdy działają tak, jak działają, https://www.infoq.com/articles/Java-8-Lambdas-A-Peek-Under-the-Hood ma szczegółowy wygląd.
źródło
Jeśli nie chcesz tworzyć pola, ponieważ potrzebujesz go tylko lokalnie, możesz przechowywać je w anonimowej klasie:
int runCount = new Object() { int runCount = 0; { myStream.stream() .filter(...) .peek(x -> runCount++) .forEach(...); } }.runCount;
Wiem, dziwne. Ale utrzymuje zmienną tymczasową poza zasięgiem lokalnym.
źródło
redukuje również działa, możesz go używać w ten sposób
źródło
Inną alternatywą jest użycie 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");
źródło
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
źródło