2015-03-01 20 views
36

Często mam ten sam problem. Muszę policzyć przebiegi lambda do użycia poza lambdą. Np .:Java 8: Preferowany sposób liczenia iteracji lambda?

myStream.stream().filter(...).forEach(item->{ ... ; runCount++); 
System.out.println("The lambda ran "+runCount+"times"); 

Problem polega na tym, że runCount musi być ostateczny, więc nie może być int. Nie może to być liczba całkowita, ponieważ jest niezmienna. Mogłabym utworzyć zmienną na poziomie klasy (tj. Pole), ale potrzebuję jej tylko w tym bloku kodu. Wiem, że są różne sposoby, jestem po prostu ciekawy, jakie jest dla ciebie preferowane rozwiązanie? Czy używasz AtomicInteger lub odwołania do tablicy lub w inny sposób?

+0

Witaj, to jest duplikat pytania: http://stackoverflow.com/questions/23157842/cast-regular-int-to-final-java – Sliver2009

+3

@ Sliver2009 Nie, nie jest. –

+4

@Florian Musisz użyć 'AtomicInteger' tutaj. –

Odpowiedz

37

Pozwól sformatować Twój przykład trochę dla dobra 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ę trzeba zwiększyć licznik od wewnątrz lambda, typowy sposób, aby to zrobić jest, aby licznik AtomicInteger lub AtomicLong, a następnie wywołaj jedną z metod inkrementacji na nim.

Można użyć jednoczęściowej macierzy int lub long, ale będzie to miało warunki wyścigu, jeśli strumień jest uruchamiany równolegle.

Należy jednak zauważyć, że strumień kończy się forEach, co oznacza, że ​​nie ma zwracanej wartości. Można zmienić forEach do peek, który przechodzi poprzez elementy, a następnie policzyć:

long runCount = myStream.stream() 
    .filter(...) 
    .peek(item -> { 
     foo(); 
     bar(); 
    }) 
    .count(); 
System.out.println("The lambda ran " + runCount + " times"); 

Jest to nieco lepiej, ale nadal nieco dziwne. Powodem jest to, że forEach i peek mogą wykonywać swoją pracę tylko poprzez efekty uboczne. Pojawiający się styl funkcjonalny Java 8 ma na celu uniknięcie efektów ubocznych. Zrobiliśmy trochę tego poprzez wyodrębnienie przyrostu licznika do operacji count w strumieniu. Inne typowe efekty 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ę sugerować niczego bardziej konkretnego.

+0

Świetna odpowiedź, dzięki! –

+4

Należy zauważyć, że 'peek' przestaje działać, po tym jak implementacje' count' zaczną używać skrótów dla strumieni 'SIZED'. To może nigdy nie być problem ze strumieniem 'filter'ed, ale może stworzyć wielkie niespodzianki, jeśli ktoś zmieni kod w późniejszym czasie ... – Holger

+4

Deklaracja' final AtomicInteger i = new AtomicInteger (1); 'i gdzieś w twoim użyciu lambda' i. getAndAdd (1) '. Zatrzymaj i zapamiętaj, jak miło 'int i = 1; ... i ++ "kiedyś było. – aliopi

3
AtomicInteger runCount = 0L; 
long runCount = myStream.stream() 
    .filter(...) 
    .peek(item -> { 
     foo(); 
     bar(); 
    }) 
    .count(); 
System.out.println("The lambda ran " + runCount.incrementAndGet() + "times"); 
+10

Proszę [edytuj] z dodatkowymi informacjami. Tylko kod i odpowiedzi "spróbuj tego" są zniechęcane (// meta.stackexchange.com/questions/196187), ponieważ nie zawierają treści do wyszukiwania i nie wyjaśniają, dlaczego ktoś powinien "spróbować tego". Staramy się być źródłem wiedzy. – Mogsdad

+5

Twoja odpowiedź mnie wprawia w zakłopotanie. Masz dwie zmienne o nazwie "runCount". Podejrzewam, że zamierzałeś mieć tylko jednego z nich, ale który? –

+1

Znalazłem bardziej odpowiedni dla runCount.getAndIncrement(). Świetna odpowiedź! – kospol

12

Jako alternatywę dla synchronizacji zaczepiający jednego AtomicInteger może wykorzystać układ całkowitą z niego. Tak długo, jak odniesienie do tablicy nie dostać kolejną tablicę przypisaną (i to jest punkt) może być używany jako ostateczny zmiennej podczas gdy wartości pól może zmienić arbitralnie.

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 
    }); 
+2

Nie działa to w przypadku strumieni równoległych ... – pisaruk

+0

Jeśli chcesz się upewnić, że odwołanie nie zostanie przypisane do innej tablicy, możesz zadeklarować iarr jako zmienną końcową. Ale jak zaznacza @pisaruk, to nie będzie działać równolegle. – themathmagician

+0

Myślę, że dla prostego "foreach" bezpośrednio przy zbieraniu (bez strumieni), jest to wystarczająco dobre podejście. dzięki !! –

2

Innym sposobem osiągnięcia tego celu (przydatne, jeśli chcesz, aby Twój licznik zwiększany tylko w niektórych przypadkach, na przykład jeśli operacja się powiodła) jest mniej więcej tak, używając mapToInt() i sum():

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 wciąż dość dziwne, ponieważ nie unika całkowicie skutków ubocznych (w zależności od tego, co robią foo() i bar()).

A inny sposób zwiększając zmiennej w lambda, która jest dostępna na zewnątrz to jest użycie klasy zmiennej:

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, stosując zmienną klasy na liczniku w jednej metody prawdopodobnie nie robi” ma sens, więc ostrzegałbym go, chyba że jest ku temu dobry powód. Zachowanie zmiennych klasowych final, jeśli to możliwe, może być pomocne w kwestii bezpieczeństwa wątków, itp. (Zobacz http://www.javapractices.com/topic/TopicAction.do?Id=23, aby uzyskać dyskusję na temat korzystania z final).

Aby uzyskać lepszy obraz tego, dlaczego lambdas działa tak jak oni, https://www.infoq.com/articles/Java-8-Lambdas-A-Peek-Under-the-Hood ma szczegółowy wygląd.

0

Jeśli nie chcesz, aby utworzyć pole, ponieważ tylko trzeba go lokalnie, można przechowywać go w anonimowej klasy:

int runCount = new Object() { 
    int runCount = 0; 
    { 
     myStream.stream() 
       .filter(...) 
       .peek(x -> runCount++) 
       .forEach(...); 
    } 
}.runCount; 

Dziwne, wiem. Ale zachowuje zmienną tymczasową nawet z zasięgu lokalnego.

Powiązane problemy