2015-03-01 19 views
36

Ho spesso lo stesso problema. Devo contare le corse di una lambda per l'uso al di fuori della lambda. Es .:Java 8: modo preferito per contare le iterazioni di una lambda?

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

Il problema è che RunCount deve essere definitiva, quindi non può essere un int. Non può essere un numero intero perché è immutabile. Potrei renderlo variabile di livello di classe (ad esempio un campo) ma ne avrò bisogno solo in questo blocco di codice. So che ci sono vari modi, sono solo curioso di sapere qual è la tua soluzione preferita per questo? Usi un oggetto AtomicInteger o un riferimento di matrice o un altro modo?

+0

Ciao, questa è una domanda duplicata: http://stackoverflow.com/questions/23157842/cast-regular-int-to-final-java – Sliver2009

+3

@ Sliver2009 No non lo è. –

+4

@Florian Devi usare 'AtomicInteger' qui. –

risposta

37

Permettetemi di riformattare il vostro esempio un po 'per il bene della discussione:

long runCount = 0L; 
myStream.stream() 
    .filter(...) 
    .forEach(item -> { 
     foo(); 
     bar(); 
     runCount++; // doesn't work 
    }); 
System.out.println("The lambda ran " + runCount + " times"); 

Se davvero bisogno di incrementare un contatore all'interno di una lambda, il tipico modo per farlo è quello di rendere il contatore un AtomicInteger o AtomicLong e quindi chiamare uno dei metodi di incremento su di esso.

È possibile utilizzare un array a elemento singolo int o long, ma questo avrebbe condizioni di competizione se il flusso viene eseguito in parallelo.

Ma si noti che il flusso termina in forEach, il che significa che non vi è alcun valore di ritorno. Si potrebbe cambiare il forEach ad un peek, che passa attraverso gli oggetti, e poi li contate:

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

Questo è un po 'meglio, ma ancora un po' strano. Il motivo è che forEach e peek possono fare il loro lavoro solo tramite effetti collaterali. Lo stile funzionale emergente di Java 8 è quello di evitare effetti collaterali. Abbiamo fatto un po 'di ciò estraendo l'incremento del contatore in un'operazione count sullo stream. Altri effetti collaterali tipici sono l'aggiunta di elementi alle raccolte. Di solito questi possono essere sostituiti tramite l'uso di collezionisti. Ma senza sapere quale lavoro effettivo stai cercando di fare, non posso suggerire nulla di più specifico.

+0

Ottima risposta, grazie! –

+4

Va notato che 'peek' smette di funzionare, una volta che le implementazioni di count' iniziano a usare scorciatoie per i flussi di' SIZED'. Questo potrebbe non essere mai un problema con 'filter'ed stream ma può creare grandi sorprese se qualcuno cambia il codice in un secondo momento ... – Holger

+4

Declare' final AtomicInteger i = new AtomicInteger (1); 'e da qualche parte nella tua lambda usa' i. getAndAdd (1) '. Fermati e ricorda quanto è bello 'int i = 1; ... I ++ era solito essere. – 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

Si prega di [modificare] con ulteriori informazioni. Le risposte solo per codice e "prova questo" sono [scoraggiate] (// meta.stackexchange.com/questions/196187), perché non contengono contenuti ricercabili e non spiegano perché qualcuno dovrebbe "provare questo". Facciamo uno sforzo qui per essere una risorsa per la conoscenza. – Mogsdad

+5

La tua risposta mi confonde. Hai due variabili entrambe denominate 'runCount'. Sospetto che tu avessi intenzione di avere solo uno di loro, ma quale? –

+1

Ho trovato che runCount.getAndIncrement() è più adatto. Bella risposta! – kospol

12

Come alternativa alla sincronizzazione hassling AtomicInteger si potrebbe utilizzare una matrice intero invece. Finché il riferimento alla matrice non ottiene altro array assegnato (e questo è il punto) può essere utilizzato come variabile finalementre i valori dei campi possono cambiare arbitrariamente.

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

Questo non funzionerebbe per i flussi paralleli ... – pisaruk

+0

Se si desidera assicurarsi che il riferimento non ottenga un altro array assegnato, è possibile dichiarare iarr come variabile finale. Ma come sottolinea @pisaruk, questo non funzionerà in parallelo. – themathmagician

+0

Penso che, per semplice 'foreach' direttamente sulla raccolta (senza flussi), questo è un approccio abbastanza buono. Grazie !! –

2

Un altro modo di fare questo (utile se si desidera il valore del tuo essere incrementato solo in alcuni casi, come se un operazione ha avuto successo) è qualcosa di simile, utilizzando mapToInt() e 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"); 

Come notato da Stuart Marks, questo è ancora un po 'strano, perché non evita completamente gli effetti collaterali (a seconda di cosa stanno facendo foo() e bar()).

e un altro modo di incrementare una variabile in un lambda che è accessibile al di fuori di esso è quello di utilizzare una variabile di classe:

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 questo esempio, utilizzando una variabile di classe per un contatore in un metodo probabilmente doesn' Ho un senso, quindi lo metterei in guardia, a meno che non ci sia una buona ragione per farlo. Mantenere le variabili di classe final se possibile può essere utile in termini di sicurezza del thread, ecc. (Vedere http://www.javapractices.com/topic/TopicAction.do?Id=23 per una discussione sull'utilizzo di final).

Per avere una migliore idea del perché i lambda funzionano come loro, https://www.infoq.com/articles/Java-8-Lambdas-A-Peek-Under-the-Hood ha un aspetto dettagliato.

0

Se non si desidera creare un campo perché hai solo bisogno a livello locale, è possibile memorizzare in una classe anonima:

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

Strano, lo so. Ma mantiene la variabile temporanea anche dall'ambito locale.

Problemi correlati