A menudo me enfrento al mismo problema. Necesito contar las ejecuciones de una lambda para usar fuera de la lambda. P.ej:
myStream.stream().filter(...).forEach(item->{ ... ; runCount++);
System.out.println("The lambda ran "+runCount+"times");
El problema es que runCount debe ser final, por lo que no puede ser un int. No puede ser un entero porque es inmutable. Podría convertirlo en una variable de nivel de clase (es decir, un campo) pero solo lo necesitaré en este bloque de código. Sé que hay varias formas, solo tengo curiosidad por saber cuál es su solución preferida para esto. ¿Utiliza un AtomicInteger o una referencia de matriz o de alguna otra manera?
java
lambda
java-8
java-stream
Stuart Marks
fuente
fuente
AtomicInteger
aquí.Respuestas:
Permíteme reformatear un poco tu ejemplo por el bien de la discusión:
long runCount = 0L; myStream.stream() .filter(...) .forEach(item -> { foo(); bar(); runCount++; // doesn't work }); System.out.println("The lambda ran " + runCount + " times");
Si realmente necesita incrementar un contador desde dentro de una lambda, la forma típica de hacerlo es convertir el contador en
AtomicInteger
oAtomicLong
y luego llamar a uno de los métodos de incremento en él.Podría usar un elemento único
int
o unalong
matriz, pero eso tendría condiciones de carrera si la transmisión se ejecuta en paralelo.Pero observe que el flujo termina en
forEach
, lo que significa que no hay valor de retorno. Puede cambiarforEach
a apeek
, que pasa los elementos y luego contarlos:long runCount = myStream.stream() .filter(...) .peek(item -> { foo(); bar(); }) .count(); System.out.println("The lambda ran " + runCount + " times");
Esto es algo mejor, pero todavía un poco extraño. La razón es que
forEach
ypeek
solo pueden hacer su trabajo a través de efectos secundarios. El estilo funcional emergente de Java 8 es evitar efectos secundarios. Hicimos un poco de eso extrayendo el incremento del contador en unacount
operación en la secuencia. Otros efectos secundarios típicos son la adición de elementos a las colecciones. Por lo general, estos se pueden reemplazar mediante el uso de colectores. Pero sin saber qué trabajo real está tratando de hacer, no puedo sugerir nada más específico.fuente
peek
deja de funcionar, una vez que lascount
implementaciones comienzan a utilizar atajos para lasSIZED
transmisiones. Puede que eso nunca sea un problema confilter
ed stream, pero puede crear grandes sorpresas si alguien cambia el código en un momento posterior…final AtomicInteger i = new AtomicInteger(1);
y en algún lugar de su uso de lambdai.getAndAdd(1)
. Detente y recuerda lo agradable queint i=1; ... i++
solía ser.Incrementable
en clases numéricas, incluyendoAtomicInteger
, y declarara operadores como++
funciones de aspecto elegante, no necesitaríamos sobrecargar operadores y aún tendríamos un código muy legible.Como alternativa a la sincronización de AtomicInteger, se podría usar una matriz de enteros . Siempre que la referencia a la matriz no obtenga otra matriz asignada (y ese es el punto), se puede usar como una variable final mientras que los valores de los campos pueden cambiar 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 });
fuente
foreach
directamente en la colección (sin flujos), este es un enfoque bastante bueno. Gracias !!AtomicInteger runCount = 0L; long runCount = myStream.stream() .filter(...) .peek(item -> { foo(); bar(); runCount.incrementAndGet(); }); System.out.println("The lambda ran " + runCount.incrementAndGet() + "times");
fuente
runCount
. Sospecho que tenía la intención de tener solo uno de ellos, pero ¿cuál?AtomicInteger
me ayudó pero me gustaría init connew AtomicInteger(0)
No debe usar AtomicInteger, no debe usar cosas a menos que tenga una buena razón para hacerlo. Y la razón para usar AtomicInteger podría ser solo permitir accesos concurrentes o similares.
Cuando se trata de su problema;
El soporte se puede usar para mantenerlo e incrementarlo dentro de una lambda. Y después puede obtenerlo llamando a 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");
fuente
javax.xml.ws.Holder
.Para mí, esto funcionó, espero que alguien lo encuentre útil:
AtomicInteger runCount= new AtomicInteger(0); myStream.stream().filter(...).forEach(item->{ ... ; runCount.getAndIncrement();); System.out.println("The lambda ran "+runCount.get()+"times");
getAndIncrement () La documentación de Java dice:
fuente
Otra forma de hacer esto (útil si desea que su recuento solo se incremente en algunos casos, como si una operación fue exitosa) es algo como esto, usando
mapToInt()
ysum()
:int count = myStream.stream() .filter(...) .mapToInt(item -> { foo(); if (bar()){ return 1; } else { return 0; }) .sum(); System.out.println("The lambda ran " + count + "times");
Como señaló Stuart Marks, esto todavía es algo extraño, porque no evita completamente los efectos secundarios (dependiendo de qué
foo()
ybar()
estamos haciendo).Y otra forma de incrementar una variable en una lambda que es accesible fuera de ella es usar una variable de clase:
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++; }); } }
En este ejemplo, usar una variable de clase para un contador en un método probablemente no tenga sentido, así que lo advierto a menos que haya una buena razón para hacerlo. Mantener las variables de clase,
final
si es posible, puede ser útil en términos de seguridad de subprocesos, etc. (consulte http://www.javapractices.com/topic/TopicAction.do?Id=23 para una discusión sobre el usofinal
).Para tener una mejor idea de por qué las lambdas funcionan de la forma en que lo hacen, https://www.infoq.com/articles/Java-8-Lambdas-A-Peek-Under-the-Hood tiene una vista detallada.
fuente
Si no desea crear un campo porque solo lo necesita localmente, puede almacenarlo en una clase anónima:
int runCount = new Object() { int runCount = 0; { myStream.stream() .filter(...) .peek(x -> runCount++) .forEach(...); } }.runCount;
Extraño, lo sé. Pero mantiene la variable temporal incluso fuera del ámbito local.
fuente
reducir también funciona, puedes usarlo así
fuente
Otra alternativa es usar 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");
fuente
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
fuente