procesamiento - stream collection java
¿Cómo depurar stream(). Map(...) con expresiones lambda? (4)
En nuestro proyecto estamos migrando a Java 8 y estamos probando las nuevas características de la misma.
En mi proyecto estoy usando predicados y funciones de Guava para filtrar y transformar algunas colecciones usando Collections2.transform
y Collections2.filter
.
En esta migración, necesito cambiar, por ejemplo, el código de guayaba a java 8 cambios. Entonces, los cambios que estoy haciendo son del tipo de:
List<Integer> naturals = Lists.newArrayList(1,2,3,4,5,6,7,8,9,10,11,12,13);
Function <Integer, Integer> duplicate = new Function<Integer, Integer>(){
@Override
public Integer apply(Integer n)
{
return n * 2;
}
};
Collection result = Collections2.transform(naturals, duplicate);
A...
List<Integer> result2 = naturals.stream()
.map(n -> n * 2)
.collect(Collectors.toList());
Usando guava, fui muy cómodo depurando el código, ya que podía depurar cada proceso de transformación, pero mi preocupación es cómo depurar, por ejemplo, .map(n -> n*2)
.
Usando el depurador puedo ver algunos códigos como:
@Hidden
@DontInline
/** Interpretively invoke this form on the given arguments. */
Object interpretWithArguments(Object... argumentValues) throws Throwable {
if (TRACE_INTERPRETER)
return interpretWithArgumentsTracing(argumentValues);
checkInvocationCounter();
assert(arityCheck(argumentValues));
Object[] values = Arrays.copyOf(argumentValues, names.length);
for (int i = argumentValues.length; i < values.length; i++) {
values[i] = interpretName(names[i], values);
}
return (result < 0) ? null : values[result];
}
Pero no es tan directo como Guava depurar el código, en realidad no pude encontrar la transformación n * 2
.
¿Hay alguna forma de ver esta transformación o una forma de depurar fácilmente este código?
EDITAR: He agregado una respuesta de diferentes comentarios y respuestas publicadas
Gracias al comentario de Holger
que respondió mi pregunta, el enfoque de tener bloque lambda me permitió ver el proceso de transformación y depurar lo que sucedió dentro del cuerpo lambda:
.map(
n -> {
Integer nr = n * 2;
return nr;
}
)
Gracias a Stuart Marks
el enfoque de tener referencias de métodos también me permitió depurar el proceso de transformación:
static int timesTwo(int n) {
Integer result = n * 2;
return result;
}
...
List<Integer> result2 = naturals.stream()
.map(Java8Test::timesTwo)
.collect(Collectors.toList());
...
Gracias a la respuesta de Marlon Bernardes
noté que mi Eclipse no muestra lo que debería y el uso de peek () ayudó a mostrar los resultados.
IntelliJ tiene un plugin tan bueno para este caso como un plugin Java Stream Debugger . Te propongo que mires este: https://plugins.jetbrains.com/plugin/9696-java-stream-debugger?platform=hootsuite
Extiende la ventana de la herramienta Depurador IDEA agregando el botón Trazar cadena de secuencia actual, que se activa cuando el depurador se detiene dentro de una cadena de llamadas API Stream.
Tiene una interfaz agradable para trabajar con operaciones de flujos separados y le da la oportunidad de seguir algunos valores que debe depurar.
Intellij IDEA 15 parece hacerlo aún más fácil, permite detenerse en una parte de la línea donde está lambda, vea la primera característica: http://blog.jetbrains.com/idea/2015/06/intellij-idea-15-eap-is-open/
La depuración de lambdas también funciona bien con NetBeans. Estoy usando NetBeans 8 y JDK 8u5.
Si establece un punto de interrupción en una línea donde hay una lambda, en realidad golpeará una vez cuando se configure la tubería, y luego una vez para cada elemento de la secuencia. Con su ejemplo, la primera vez que llegue al punto de interrupción será la llamada de map()
que está configurando la canalización de la secuencia:
Puedes ver la pila de llamadas y las variables locales y los valores de los parámetros para main
como es de esperar. Si continúa avanzando, se golpea de nuevo el "mismo" punto de interrupción, excepto que esta vez está dentro de la llamada a la lambda:
Tenga en cuenta que esta vez la pila de llamadas es profunda dentro de la maquinaria de flujos, y las variables locales son los locales de la propia lambda, no el método main
incluye. (He cambiado los valores en la lista de elementos naturals
para dejar esto en claro).
Como señaló Marlon Bernardes (+1), puede usar el peek
para inspeccionar los valores a medida que avanzan en la tubería. Sin embargo, ten cuidado si estás usando esto desde una transmisión paralela. Los valores pueden imprimirse en un orden impredecible a través de diferentes hilos. Si está almacenando valores en una estructura de datos de depuración a partir de peek
, esa estructura de datos, por supuesto, tendrá que ser segura para subprocesos.
Finalmente, si está realizando una gran depuración de lambdas (especialmente lambdas de sentencias de varias líneas), podría ser preferible extraer el lambda en un método con nombre y luego consultarlo utilizando una referencia de método. Por ejemplo,
static int timesTwo(int n) {
return n * 2;
}
public static void main(String[] args) {
List<Integer> naturals = Arrays.asList(3247,92837,123);
List<Integer> result =
naturals.stream()
.map(DebugLambda::timesTwo)
.collect(toList());
}
Esto podría hacer que sea más fácil ver lo que está sucediendo mientras depura. Además, la extracción de métodos de esta manera facilita la prueba unitaria. Si tu lambda es tan complicada que necesitas atravesarla, es probable que quieras tener un montón de pruebas unitarias para ella de todos modos.
Normalmente no tengo problemas para depurar expresiones lambda mientras uso Eclipse Kepler o Intellij IDEA (usando JDK8u5). Simplemente configure un punto de interrupción y asegúrese de no inspeccionar toda la expresión lambda (inspeccione solo el cuerpo lambda).
Otro enfoque es usar peek
para inspeccionar los elementos de la transmisión:
List<Integer> naturals = Arrays.asList(1,2,3,4,5,6,7,8,9,10,11,12,13);
naturals.stream()
.map(n -> n * 2)
.peek(System.out::println)
.collect(Collectors.toList());
ACTUALIZAR:
Creo que te confundes porque el map
es una intermediate operation
; en otras palabras, es una operación lenta que se ejecutará solo después de que se ejecutó una terminal operation
. Entonces cuando llamas a stream.map(n -> n * 2)
el cuerpo lambda no se está ejecutando en este momento. Debe establecer un punto de interrupción e inspeccionarlo después de que se haya llamado a una operación de terminal ( collect
, en este caso).
Verifique las operaciones de Stream para obtener más explicaciones.
ACTUALIZACIÓN 2:
Citando Holger comentario Holger :
Lo que lo hace difícil aquí es que la llamada al mapa y la expresión lambda están en una línea, por lo que un punto de corte se detendrá en dos acciones completamente independientes.
Insertar un salto de línea inmediatamente después del
map(
le permitiría establecer un punto de corte solo para la expresión lambda. Y no es raro que los depuradores no muestren valores intermedios de una declaración dereturn
. Cambiar la lambda an -> { int result=n * 2; return result; }
le permitiría inspeccionar el resultado. De nuevo, inserte los saltos de línea de manera apropiada al avanzar línea por línea ...