streams recorrer procesamiento print parte lista ejemplo datos con java java-8 java-stream

recorrer - stream java 8 ejemplo



Java 8 stream API: Excepciones al modificar listas (4)

Al agregar algunas impresiones de depuración a la tubería, se muestra el origen de la excepción NullPointerException:

list.stream().peek(string -> System.out.println("peek1 " + string)).filter(string -> string.equalsIgnoreCase("5")).peek(string -> System.out.println("peek2 " + string)).forEach(string -> removeMember(string));

Esto produce:

peek1 0 peek1 1 peek1 2 peek1 3 peek1 4 peek1 5 peek2 5 peek1 7 peek1 8 peek1 9 peek1 null Exception in thread "main" java.lang.NullPointerException at HelloWorld.lambda$main$1(HelloWorld.java:22) at HelloWorld$$Lambda$2/303563356.test(Unknown Source) at java.util.stream.ReferencePipeline$2$1.accept(ReferencePipeline.java:174) at java.util.stream.ReferencePipeline$11$1.accept(ReferencePipeline.java:373) at java.util.ArrayList$ArrayListSpliterator.forEachRemaining(ArrayList.java:1374) at java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:512) at java.util.stream.AbstractPipeline.wrapAndCopyInto(AbstractPipeline.java:502) at java.util.stream.ForEachOps$ForEachOp.evaluateSequential(ForEachOps.java:151) at java.util.stream.ForEachOps$ForEachOp$OfRef.evaluateSequential(ForEachOps.java:174) at java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:234) at java.util.stream.ReferencePipeline.forEach(ReferencePipeline.java:418) at HelloWorld.main(HelloWorld.java:22)

Cuando se eliminó "5" de la Lista, todos los elementos de "6" a "9" se desplazaron una posición hacia la izquierda (es decir, sus índices se redujeron en 1). La canalización de Stream no lo detectó, por lo que saltó "6", y cuando procesó la última posición (que originalmente contenía "9"), encontró un valor nulo, lo que resultó en una string.equalsIgnoreCase("5") NullPointerException cuando string.equalsIgnoreCase("5") era evaluado para ello.

Esto es similar a lo que obtendrías en este bucle for tradicional:

int size = list.size(); for (int i = 0; i < size; i++) { String string = list.get(i); if (string.equalsIgnoreCase("5")) removeMember(string); }

Solo aquí obtendrías la IndexOutOfBoundsException lugar de NullPointerException , ya que list.get(i) fallaría cuando i==9 . Supongo que la canalización de Stream funciona directamente en la matriz interna de ArrayList , por lo que no detecta que el tamaño de la Lista haya cambiado.

EDITAR:

Siguiendo el comentario de Holger, cambié el código para eliminar la NullPointerException (cambiando el filtro para filter(string -> "5".equalsIgnoreCase(string)) ). Esto de hecho produce ConcurrentModificationException :

peek1 0 peek1 1 peek1 2 peek1 3 peek1 4 peek1 5 peek2 5 peek1 7 peek1 8 peek1 9 peek1 null Exception in thread "main" java.util.ConcurrentModificationException at java.util.ArrayList$ArrayListSpliterator.forEachRemaining(ArrayList.java:1380) at java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:512) at java.util.stream.AbstractPipeline.wrapAndCopyInto(AbstractPipeline.java:502) at java.util.stream.ForEachOps$ForEachOp.evaluateSequential(ForEachOps.java:151) at java.util.stream.ForEachOps$ForEachOp$OfRef.evaluateSequential(ForEachOps.java:174) at java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:234) at java.util.stream.ReferencePipeline.forEach(ReferencePipeline.java:418) at HelloWorld.main(HelloWorld.java:22)

Tomemos un ArrayList y ArrayList con algo simple:

List<String> list = new ArrayList<>(); for (int i = 0; i < 10; i++) { list.add(""+i); }

Intentaré eliminar un miembro, por ejemplo llamado 5, con diferentes formas de transmitir la API. Para esto defino el método, que me dará una ConcurentModificationException cuando use la iteración tradicional con iterador.

void removeMember(String clientListener) { list.remove(clientListener); }

Este código me da esa excepción, que entiendo:

list.parallelStream() .filter(string -> string.equalsIgnoreCase("5")) .forEach(string -> removeMember(string));

Sin embargo, al probar solo stream() , no parallelStream() obtiene una excepción de puntero nulo (NPE), lo cual es extraño para mí:

list.stream() .filter(string -> string.equalsIgnoreCase("5")) .forEach(string -> removeMember(string));

Ahora cambie el tipo de List a LinkedList<> . El último código con stream() me da una ConcurentModificationException , ¡y parallelStream() repente funciona!

Entonces, las preguntas.

  1. ¿Es la cocina interna parallelStream() (Spliterators y otra magia) lo suficientemente inteligente como para usar dicho elemento de eliminación para LinkedList ? ¿Siempre funcionará?

  2. ¿Por qué fue esa NPE para ArrayList ? Por qué NPE, no ConcurentModificationException me refiero.


Al usar Java8 Lambdas, es mejor no llevar el seguimiento de la pila a su valor nominal. El error debe leerse para comprender que una NPE está causada por alguna línea de código dentro de forEach lambda. forEach tanto, debe evaluar cada línea. y ver qué puede estar causando esto.


El comportamiento de su código es esencialmente indefinido (de ahí las diversas respuestas que obtiene). La documentación de la secuencia (sección sin interferencia) dice:

A menos que la fuente de flujo sea concurrente, modificar el origen de datos de un flujo durante la ejecución de un flujo de flujo puede causar excepciones, respuestas incorrectas o un comportamiento no conforme.

Y ArrayList y LinkedList no son concurrentes.

Podría usar una fuente concurrente, pero tendría más sentido alejarse de modificar la fuente de la secuencia, por ejemplo, utilizando la Collection#removeIf :

list.removeIf(string -> string.equalsIgnoreCase("5"));


Si desea usar secuencias, en lugar de modificar la colección original (ver inmutabilidad con su seguridad de subprocesos inherente ), solo debe recuperar una nueva lista sin ese elemento:

list.stream().filter(string -> !string.equalsIgnoreCase("5")) .collect(Collectors.toList());

¿Con respecto a su otra pregunta sobre parallelStream y si ese enfoque siempre podría funcionar?

No, definitivamente no lo hará. Las Lists que está utilizando no están diseñadas para admitir el acceso simultáneo, a veces parecerá que funciona, otras veces fallará cuando lo vea o le dará resultados "inesperados". Si sabe que varios subprocesos acceden a una estructura de datos, siempre codifique en consecuencia.