todas propagacion permite paquetes mas lista las excepciones conclusion comunes clase agregar java exception-handling java-8 java-stream

permite - propagacion de excepciones en java



Agregar excepciones de tiempo de ejecuciĆ³n en secuencias de Java 8 (4)

Aquí hay una variación sobre el tema de mapeo a excepciones.

Comience con su método doStuff existente. Tenga en cuenta que esto se ajusta a la interfaz funcional Consumer<MyObject> .

public void doStuff(MyObject myObj) { if (...) { throw new IllegalStateException("Fire! Fear! Foes! Awake!"); } // do stuff... }

Ahora escriba una función de orden superior que envuelva esto y la convierta en una función que podría o no devolver una excepción. Queremos llamar a esto desde flatMap , por lo que la manera en que se expresa "podría o no" se expresa devolviendo una secuencia que contiene la excepción o una secuencia vacía. RuntimeException como el tipo de excepción aquí, pero por supuesto podría ser cualquier cosa. (De hecho, podría ser útil usar esta técnica con las excepciones marcadas).

<T> Function<T,Stream<RuntimeException>> ex(Consumer<T> cons) { return t -> { try { cons.accept(t); return Stream.empty(); } catch (RuntimeException re) { return Stream.of(re); } }; }

Ahora reescriba doStuffOnList para usar esto dentro de una secuencia:

void doStuffOnList(List<MyObject> myObjs) { List<RuntimeException> exs = myObjs.stream() .flatMap(ex(this::doStuff)) .collect(Collectors.toList()); System.out.println("Exceptions: " + exs); }

Digamos que tengo un método que lanza una excepción de tiempo de ejecución. Estoy usando un Stream para llamar a este método en los elementos de una lista.

class ABC { public void doStuff(MyObject myObj) { if (...) { throw new IllegalStateException("Fire! Fear! Foes! Awake!"); } // do stuff... } public void doStuffOnList(List<MyObject> myObjs) { try { myObjs.stream().forEach(ABC:doStuff); } catch(AggregateRuntimeException??? are) { ... } } }

Ahora quiero que todos los elementos de la lista se procesen, y cualquier excepción de tiempo de ejecución en los artículos individuales se recopile en una excepción de tiempo de ejecución "agregado" que se lanzará al final.

En mi código real, estoy haciendo llamadas a API de terceros que pueden generar excepciones en tiempo de ejecución. Quiero asegurarme de que todos los elementos se procesen y de que se informen todos los errores al final.

Se me ocurren algunas maneras de eliminar esto, como una función map() que captura y devuelve la excepción ( ... shudder ... ). ¿Pero hay una manera nativa de hacer esto? Si no es así, ¿hay otra forma de implementarlo limpiamente?


En este simple caso donde el método doStuff es void y solo te preocupan las excepciones, puedes mantener las cosas simples:

myObjs.stream() .flatMap(o -> { try { ABC.doStuff(o); return null; } catch (RuntimeException ex) { return Stream.of(ex); } }) // now a stream of thrown exceptions. // can collect them to list or reduce into one exception .reduce((ex1, ex2) -> { ex1.addSuppressed(ex2); return ex1; }).ifPresent(ex -> { throw ex; });

Sin embargo, si sus requisitos son más complicados y prefiere quedarse con la biblioteca estándar, CompletableFuture puede servir para representar "éxito o fracaso" (aunque con algunas verrugas):

public static void doStuffOnList(List<MyObject> myObjs) { myObjs.stream() .flatMap(o -> completedFuture(o) .thenAccept(ABC::doStuff) .handle((x, ex) -> ex != null ? Stream.of(ex) : null) .join() ).reduce((ex1, ex2) -> { ex1.addSuppressed(ex2); return ex1; }).ifPresent(ex -> { throw new RuntimeException(ex); }); }


La única forma posible que puedo imaginar es asignar los valores en una lista a una mónada, que representará el resultado de su ejecución de procesamiento (ya sea éxito con valor o falla con posibilidad de lanzar). Y luego doble su flujo en un solo resultado con una lista agregada de valores o una excepción con una lista de los suprimidos de los pasos anteriores.

public Result<?> doStuff(List<?> list) { return list.stream().map(this::process).reduce(RESULT_MERGER) } public Result<SomeType> process(Object listItem) { try { Object result = /* Do the processing */ listItem; return Result.success(result); } catch (Exception e) { return Result.failure(e); } } public static final BinaryOperator<Result<?>> RESULT_MERGER = (left, right) -> left.merge(right)

La implementación del resultado puede variar, pero creo que entiendes la idea.


Ya hay algunas implementaciones de Try monad para Java. Encontré una better-java8-monads , por ejemplo. Usándolo, puedes escribir en el siguiente estilo.

Supongamos que desea asignar sus valores y realizar un seguimiento de todas las excepciones:

public String doStuff(String s) { if(s.startsWith("a")) { throw new IllegalArgumentException("Incorrect string: "+s); } return s.trim(); }

Vamos a tener alguna entrada:

List<String> input = Arrays.asList("aaa", "b", "abc ", " qqq ");

Ahora podemos asignarlos a intentos exitosos y pasarlos a su método, luego recopilar los datos y fallos manejados con éxito por separado:

Map<Boolean, List<Try<String>>> result = input.stream() .map(Try::successful).map(t -> t.map(this::doStuff)) .collect(Collectors.partitioningBy(Try::isSuccess));

Después de eso puedes procesar entradas exitosas:

System.out.println(result.get(true).stream() .map(t -> t.orElse(null)).collect(Collectors.joining(",")));

Y hacer algo con todas las excepciones:

result.get(false).stream().forEach(t -> t.onFailure(System.out::println));

La salida es:

b,qqq java.lang.IllegalArgumentException: Incorrect string: aaa java.lang.IllegalArgumentException: Incorrect string: abc

Personalmente no me gusta cómo está diseñada esta biblioteca, pero probablemente será adecuada para ti.

Aquí hay una gist con el ejemplo completo.